yottadb/simple_api/
mod.rs

1/****************************************************************
2*                                                               *
3* Copyright (c) 2019-2024 YottaDB LLC and/or its subsidiaries.  *
4* All rights reserved.                                          *
5*                                                               *
6*       This source code contains the intellectual property     *
7*       of its copyright holder(s), and is made available       *
8*       under a license.  If you do not know the terms of       *
9*       the license, please stop and do not read further.       *
10*                                                               *
11****************************************************************/
12
13//! This is the main implementation of the YDBRust wrapper.
14//!
15//! The API is not particularly friendly, but it exposes only safe code.
16//!
17//! Most operations are encapsulated in methods on the [`Key`] struct, and generally
18//! consume a `Vec<u8>` and return [`YDBResult<Vec<u8>>`][YDBResult]. The return `Vec<u8>` will either contain
19//! the data fetched from the database or an error.
20//!
21//! The `Vec<u8>` may be resized as part of the call.
22
23pub mod call_in;
24
25use std::{error::Error, marker::PhantomData};
26use std::ops::{Deref, DerefMut, Index, IndexMut};
27use std::ptr;
28use std::ffi::CString;
29use std::os::raw::{c_void, c_int};
30use std::time::Duration;
31use std::cmp::min;
32use std::fmt;
33use std::error;
34use std::mem;
35use std::panic;
36use crate::craw::{
37    ydb_buffer_t, ydb_get_st, ydb_set_st, ydb_data_st, ydb_delete_st, ydb_message_t, ydb_incr_st,
38    ydb_node_next_st, ydb_node_previous_st, ydb_subscript_next_st, ydb_subscript_previous_st,
39    ydb_tp_st, YDB_OK, YDB_ERR_INVSTRLEN, YDB_ERR_INSUFFSUBS, YDB_ERR_TPRETRY, YDB_DEL_TREE,
40    YDB_DEL_NODE, YDB_TP_RESTART, YDB_TP_ROLLBACK,
41};
42
43const DEFAULT_CAPACITY: usize = 50;
44
45/// An error returned by the underlying YottaDB library.
46///
47/// This error cannot be constructed manually.
48#[derive(Clone, Hash, Eq, PartialEq)]
49pub struct YDBError {
50    /// YottaDB internally uses an error-handling mechanism similar to `errno` and `perror`.
51    /// Since, in a threaded context, another error may occur before the application has
52    /// a chance to call `perror()` (in YottaDB, [`$ZSTATUS`]),
53    /// the stringified error must be returned at the same time as the status code.
54    ///
55    /// [`$ZSTATUS`]: https://docs.yottadb.com/MultiLangProgGuide/MultiLangProgGuide.html#zstatus
56    pub message: Vec<u8>,
57    /// The status returned by a YottaDB function. This will be a `YDB_ERR_*` constant.
58    ///
59    /// ## See also
60    /// - [ZMessage Codes](https://docs.yottadb.com/MessageRecovery/errormsgref.html#zmessage-codes)
61    pub status: i32,
62    /// The [transaction] that was in process when the error occurred.
63    ///
64    /// [transaction]: https://docs.yottadb.com/MultiLangProgGuide/MultiLangProgGuide.html#transaction-processing
65    tptoken: TpToken,
66}
67
68impl fmt::Debug for YDBError {
69    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70        write!(f, "YDB Error ({}): {}", self.status, String::from_utf8_lossy(&self.message))
71    }
72}
73
74impl fmt::Display for YDBError {
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        let tmp;
77        let message = match message_t(self.tptoken, Vec::new(), self.status) {
78            Ok(buf) => {
79                tmp = buf;
80                String::from_utf8_lossy(&tmp)
81            }
82            Err(err) => {
83                std::borrow::Cow::from(format!("<error retrieving error message: {}>", err.status))
84            }
85        };
86        write!(f, "YDB Error ({}): {}", message, &String::from_utf8_lossy(&self.message))
87    }
88}
89
90impl error::Error for YDBError {}
91
92/// A specialized `Result` type returned by a YottaDB function.
93pub type YDBResult<T> = Result<T, YDBError>;
94
95/// A transaction processing token, used by yottadb to ensure ACID properties.
96///
97/// The only valid values for a TpToken are the default (`TpToken::default()`)
98/// or a token passed in from [`Context::tp`](crate::Context::tp).
99///
100/// TpTokens can be converted to `u64`, but not vice-versa.
101#[derive(Copy, Clone, Hash, Eq, PartialEq)]
102pub struct TpToken(
103    // This is private to prevent users creating their own TpTokens. YDB has unpredictable behavior
104    // when passed the wrong tptoken, such as infinite loops and aborts.
105    pub(crate) u64,
106);
107
108impl fmt::Debug for TpToken {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        let token: &dyn fmt::Display = if self.0 == 0 { &"YDB_NOTTP" } else { &self.0 };
111        write!(f, "TpToken({})", token)
112    }
113}
114
115impl Default for TpToken {
116    fn default() -> Self {
117        TpToken(crate::craw::YDB_NOTTP)
118    }
119}
120
121impl From<TpToken> for u64 {
122    /// This is useful for calling C functions that have not yet been wrapped in the `simple_api`
123    /// from inside a transaction.
124    ///
125    /// # Example
126    /// ```
127    /// use yottadb::*;
128    /// use yottadb::craw::ydb_buffer_t;
129    /// Context::new().tp(|ctx| {
130    ///   let tptoken_raw = u64::from(ctx.tptoken());
131    ///   let mut errstr = ydb_buffer_t {
132    ///     buf_addr: std::ptr::null_mut(),
133    ///     len_alloc: 0,
134    ///     len_used: 0,
135    ///   };
136    ///   unsafe { craw::ydb_stdout_stderr_adjust_t(tptoken_raw, &mut errstr) };
137    ///   Ok(TransactionStatus::Ok)
138    /// }, "BATCH", &[]);
139    /// ```
140    fn from(tptoken: TpToken) -> u64 {
141        tptoken.0
142    }
143}
144
145/// The type of data available at the current node.
146///
147/// # See also
148/// - [`KeyContext::data()`](crate::KeyContext::data)
149#[derive(Debug, Clone, Eq, PartialEq, Hash)]
150pub enum DataReturn {
151    /// There is no data present, either here or lower in the tree.
152    NoData,
153    /// There is data present at this node, but not lower in the tree.
154    ValueData,
155    /// There is data present lower in the tree, but not at this node.
156    TreeData,
157    /// There is data present both at this node and lower in the tree.
158    ValueTreeData,
159}
160
161/// The type of deletion that should be carried out.
162///
163/// # See also
164/// - [`KeyContext::delete_st()`](crate::KeyContext::delete)
165#[derive(Debug, Clone, Hash, Eq, PartialEq)]
166pub enum DeleteType {
167    /// Delete only this node.
168    DelNode,
169    /// Delete this node and all subnodes in the tree.
170    DelTree,
171}
172
173/// Provides a [`Key`] object for the given subscripts.
174///
175/// See [the YottaDB documentation][nodes-and-variables] for more information
176/// about how YottaDB handles keys.
177///
178/// # Examples
179///
180/// Make a simple key:
181/// ```
182/// # #[macro_use] extern crate yottadb;
183/// let my_key = make_key!("^MyTimeSeriesData", "5");
184/// ```
185///
186/// Keys must have at least one variable:
187/// ```compile_fail
188/// let mut key = make_key!();
189/// ```
190///
191/// [nodes-and-variables]: https://docs.yottadb.com/MultiLangProgGuide/MultiLangProgGuide.html#keys-values-nodes-variables-and-subscripts
192#[macro_export]
193macro_rules! make_key {
194    ( $var:expr $(,)? ) => (
195        $crate::Key::variable($var)
196    );
197    ( $var: expr $( , $subscript: expr)+ $(,)? ) => (
198        $crate::Key::new($var, &[
199            $($subscript),*
200        ])
201    );
202}
203
204/// A key used to get, set, and delete values in the database.
205///
206/// # See also
207/// - [`KeyContext`](super::context_api::KeyContext)
208/// - [Keys, values, nodes, variables, and subscripts](https://docs.yottadb.com/MultiLangProgGuide/MultiLangProgGuide.html#keys-values-nodes-variables-and-subscripts)
209/// - [Local and Global variables](https://docs.yottadb.com/MultiLangProgGuide/MultiLangProgGuide.html#local-and-global-variables)
210/// - [Intrinsic special variables](https://docs.yottadb.com/MultiLangProgGuide/MultiLangProgGuide.html#intrinsic-special-variables)
211#[derive(Clone, Hash, Eq, PartialEq)]
212pub struct Key {
213    /// The [variable] of the key, which can be freely modified.
214    ///
215    /// Note that not all variables are valid.
216    /// If a `variable` is set to an invalid value, the next call to YottaDB
217    /// will result in a [`YDB_ERR_INVVARNAME`](super::craw::YDB_ERR_INVVARNAME).
218    /// See [variables vs. subscripts][variable] for details on what variables are valid and invalid.
219    ///
220    /// [variable]: https://docs.yottadb.com/MultiLangProgGuide/MultiLangProgGuide.html#variables-vs-subscripts-vs-values
221    pub variable: String,
222    subscripts: Vec<Vec<u8>>,
223}
224
225impl fmt::Debug for Key {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        use std::fmt::Write;
228
229        f.write_str(&self.variable)?;
230        if let Some(first) = self.subscripts.first() {
231            write!(f, "({:?}", String::from_utf8_lossy(first))?;
232            for subscript in &self.subscripts[1..] {
233                write!(f, ", {:?}", String::from_utf8_lossy(subscript))?;
234            }
235            f.write_char(')')?;
236        }
237        Ok(())
238    }
239}
240
241impl Key {
242    // public for `make_ckey!`
243    #[doc(hidden)]
244    /// Create a new key.
245    pub fn new<V, S>(variable: V, subscripts: &[S]) -> Key
246    where
247        V: Into<String>,
248        S: Into<Vec<u8>> + Clone,
249    {
250        Key {
251            variable: variable.into(),
252            // NOTE: we cannot remove this copy because `node_next_st` mutates subscripts
253            // and `node_subscript_st` mutates the variable
254            subscripts: subscripts.iter().cloned().map(|slice| slice.into()).collect(),
255        }
256    }
257
258    // public for `make_ckey!`
259    #[doc(hidden)]
260    /// Shortcut for creating a key with no subscripts.
261    pub fn variable<V: Into<String>>(var: V) -> Key {
262        Key::new::<V, Vec<u8>>(var, &[])
263    }
264
265    /// Gets the value of this key from the database and returns the value.
266    ///
267    /// See also [KeyContext::get](crate::context_api::KeyContext::get).
268    #[inline]
269    pub(crate) fn get_st(&self, tptoken: TpToken, out_buffer: Vec<u8>) -> YDBResult<Vec<u8>> {
270        self.direct_unsafe_call(tptoken, out_buffer, ydb_get_st)
271    }
272
273    /// Sets the value of a key in the database.
274    ///
275    /// See also [KeyContext::set](crate::context_api::KeyContext::set).
276    pub(crate) fn set_st<U>(
277        &self, tptoken: TpToken, err_buffer: Vec<u8>, new_val: U,
278    ) -> YDBResult<Vec<u8>>
279    where
280        U: AsRef<[u8]>,
281    {
282        let new_val = new_val.as_ref();
283        let new_val_t = ydb_buffer_t {
284            buf_addr: new_val.as_ptr() as *const _ as *mut _,
285            len_alloc: new_val.len() as u32,
286            len_used: new_val.len() as u32,
287        };
288        let do_call = |tptoken, out_buffer_p, varname_p, len, subscripts_p| unsafe {
289            ydb_set_st(tptoken, out_buffer_p, varname_p, len, subscripts_p, &new_val_t)
290        };
291        self.non_allocating_call(tptoken, err_buffer, do_call)
292    }
293
294    /// Returns information about a local or global variable node.
295    ///
296    /// See also [KeyContext::data](crate::context_api::KeyContext::data).
297    pub(crate) fn data_st(
298        &self, tptoken: TpToken, err_buffer: Vec<u8>,
299    ) -> YDBResult<(DataReturn, Vec<u8>)> {
300        let mut retval: u32 = 0;
301        let do_call = |tptoken, out_buffer_p, varname_p, len, subscripts_p| unsafe {
302            ydb_data_st(tptoken, out_buffer_p, varname_p, len, subscripts_p, &mut retval as *mut _)
303        };
304        let err_buffer = self.non_allocating_call(tptoken, err_buffer, do_call)?;
305        let data_ret = match retval {
306            0 => DataReturn::NoData,
307            1 => DataReturn::ValueData,
308            10 => DataReturn::TreeData,
309            11 => DataReturn::ValueTreeData,
310            // If it's not one of these values, there is something wrong with the API
311            //  and we need to address it. Returning an Err here won't make things
312            //  more clear because the error code is not one of YottaDB's
313            _ => panic!(
314                "Unexpected return from ydb_data_st: {}, ZSTATUS: {}",
315                retval,
316                String::from_utf8_lossy(&err_buffer)
317            ),
318        };
319        Ok((data_ret, err_buffer))
320    }
321
322    /// Delete nodes in the local or global variable tree or subtree specified.
323    ///
324    /// See also [KeyContext::delete](crate::context_api::KeyContext::delete).
325    pub(crate) fn delete_st(
326        &self, tptoken: TpToken, err_buffer: Vec<u8>, delete_type: DeleteType,
327    ) -> YDBResult<Vec<u8>> {
328        let c_delete_ty = match delete_type {
329            DeleteType::DelNode => YDB_DEL_NODE,
330            DeleteType::DelTree => YDB_DEL_TREE,
331        } as i32;
332        self.non_allocating_call(
333            tptoken,
334            err_buffer,
335            |tptoken, out_buffer_p, varname_p, len, subscripts_p| unsafe {
336                ydb_delete_st(tptoken, out_buffer_p, varname_p, len, subscripts_p, c_delete_ty)
337            },
338        )
339    }
340
341    // A call that can reallocate the `out_buffer`, but cannot modify `self`.
342    //
343    // `non_allocating_call` assumes that there are no extant references to `out_buffer`.
344    // Functions that require a separate `err_buffer_t` cannot use `non_allocating_call`.
345    //
346    // `non_allocating_call` assumes that on error, `func` should be called again.
347    // Functions which require `func` to only be called once cannot use `non_allocating_call`.
348    fn non_allocating_call<F>(
349        &self, tptoken: TpToken, err_buffer: Vec<u8>, mut func: F,
350    ) -> YDBResult<Vec<u8>>
351    where
352        F: FnMut(u64, *mut ydb_buffer_t, *const ydb_buffer_t, i32, *const ydb_buffer_t) -> c_int,
353    {
354        self.non_allocating_ret_call(
355            tptoken,
356            err_buffer,
357            |tptoken, err_buffer_p, varname_p, len, subscripts_p, out_buffer_p| {
358                let status = func(tptoken, err_buffer_p, varname_p, len, subscripts_p);
359                unsafe {
360                    (*out_buffer_p).len_used = (*err_buffer_p).len_used;
361                }
362                status
363            },
364        )
365    }
366
367    // A call that can reallocate the `out_buffer`, but cannot modify `self`.
368    //
369    // `non_allocating_ret_call` assumes that there are no extant references to `out_buffer`.
370    //
371    // `non_allocating_ret_call` assumes that on error, `func` should be called again.
372    // Functions which require `func` to only be called once cannot use `non_allocating_ret_call`.
373    //
374    // This differs from `non_allocating_call` in that it passes an `err_buffer_t` to the closure.
375    fn non_allocating_ret_call<F>(
376        &self, tptoken: TpToken, out_buffer: Vec<u8>, mut func: F,
377    ) -> YDBResult<Vec<u8>>
378    where
379        F: FnMut(
380            u64,
381            *mut ydb_buffer_t,
382            *const ydb_buffer_t,
383            i32,
384            *const ydb_buffer_t,
385            *mut ydb_buffer_t,
386        ) -> c_int,
387    {
388        // Get pointers to the varname and to the first subscript
389        let (varname, subscripts) = self.get_buffers();
390        // Finally make the `unsafe` call
391        resize_ret_call(tptoken, out_buffer, |tptoken, err_buffer_p, out_buffer_p| {
392            func(
393                tptoken,
394                err_buffer_p,
395                varname.as_ptr(),
396                subscripts.len() as i32,
397                subscripts.as_ptr() as *const _,
398                out_buffer_p,
399            )
400        })
401    }
402
403    /// Converts the value to a number and increments it based on the value specifed by `increment`.
404    ///
405    /// See also [KeyContext::increment](crate::context_api::KeyContext::increment).
406    pub(crate) fn incr_st(
407        &self, tptoken: TpToken, out_buffer: Vec<u8>, increment: Option<&[u8]>,
408    ) -> YDBResult<Vec<u8>> {
409        let mut increment_t_buf;
410        let increment_p = if let Some(increment_v) = increment {
411            increment_t_buf = ydb_buffer_t {
412                buf_addr: increment_v.as_ptr() as *const _ as *mut _,
413                len_alloc: increment_v.len() as u32,
414                len_used: increment_v.len() as u32,
415            };
416            &mut increment_t_buf as *mut _
417        } else {
418            ptr::null_mut()
419        };
420        let mut first_run = true;
421        let do_call = |tptoken, err_buffer_p, varname_p, len, subscripts_p, out_buffer_p| {
422            if first_run {
423                first_run = false;
424                unsafe {
425                    ydb_incr_st(
426                        tptoken,
427                        err_buffer_p,
428                        varname_p,
429                        len,
430                        subscripts_p,
431                        increment_p,
432                        out_buffer_p,
433                    )
434                }
435            } else {
436                unsafe {
437                    ydb_get_st(tptoken, err_buffer_p, varname_p, len, subscripts_p, out_buffer_p)
438                }
439            }
440        };
441        self.non_allocating_ret_call(tptoken, out_buffer, do_call)
442    }
443
444    /// Decrement the count of a lock held by the process.
445    ///
446    /// See also [KeyContext::lock_decr](crate::context_api::KeyContext::lock_decr).
447    #[inline]
448    pub(crate) fn lock_decr_st(&self, tptoken: TpToken, err_buffer: Vec<u8>) -> YDBResult<Vec<u8>> {
449        use crate::craw::ydb_lock_decr_st;
450        let do_call = |tptoken, err_buffer_p, varname_p, len, subscripts_p| unsafe {
451            ydb_lock_decr_st(tptoken, err_buffer_p, varname_p, len, subscripts_p)
452        };
453        self.non_allocating_call(tptoken, err_buffer, do_call)
454    }
455
456    /// Increment the count of a lock held by the process, or acquire a new lock.
457    ///
458    /// See also [KeyContext::lock_incr](crate::context_api::KeyContext::lock_incr).
459    pub(crate) fn lock_incr_st(
460        &self, tptoken: TpToken, mut err_buffer: Vec<u8>, timeout: Duration,
461    ) -> YDBResult<Vec<u8>> {
462        use std::convert::TryInto;
463        use crate::craw::{ydb_lock_incr_st, YDB_ERR_TIME2LONG};
464
465        let timeout_ns = match timeout.as_nanos().try_into() {
466            Err(_) => {
467                // discard any previous error
468                err_buffer.clear();
469                return Err(YDBError { status: YDB_ERR_TIME2LONG, message: err_buffer, tptoken });
470            }
471            Ok(n) => n,
472        };
473        let do_call = |tptoken, err_buffer_p, varname_p, len, subscripts_p| unsafe {
474            ydb_lock_incr_st(tptoken, err_buffer_p, timeout_ns, varname_p, len, subscripts_p)
475        };
476        self.non_allocating_call(tptoken, err_buffer, do_call)
477    }
478
479    /// Facilitates depth-first traversal of a local or global variable tree, and passes itself in as the output parameter.
480    ///
481    /// See also [KeyContext::next_node_self](crate::context_api::KeyContext::next_node_self).
482    #[inline]
483    pub(crate) fn node_next_self_st(
484        &mut self, tptoken: TpToken, out_buffer: Vec<u8>,
485    ) -> YDBResult<Vec<u8>> {
486        self.growing_shrinking_call(tptoken, out_buffer, ydb_node_next_st)
487    }
488
489    /// Facilitates reverse depth-first traversal of a local or global variable tree and reports the predecessor node, passing itself in as the output parameter.
490    ///
491    /// See also [KeyContext::next_prev_self](crate::context_api::KeyContext::next_).
492    #[inline]
493    pub(crate) fn node_prev_self_st(
494        &mut self, tptoken: TpToken, out_buffer: Vec<u8>,
495    ) -> YDBResult<Vec<u8>> {
496        self.growing_shrinking_call(tptoken, out_buffer, ydb_node_previous_st)
497    }
498
499    /// A call that can change the number of subscripts (i.e. can return YDB_ERR_INSUFFSUBS).
500    ///
501    /// Most functions can only return YDB_ERR_INVSTRLEN if the subscripts don't have enough capacity
502    /// reserved, so the error handling for them is much simpler. This has error handling for
503    /// INSUFFSUBS, but is more complicated as a result, so it's only used where necessary.
504    fn growing_shrinking_call(
505        &mut self, tptoken: TpToken, mut err_buffer: Vec<u8>,
506        c_func: unsafe extern "C" fn(
507            // tptoken
508            u64,
509            // err_buffer_t
510            *mut ydb_buffer_t,
511            // varname
512            *const ydb_buffer_t,
513            // subs_used
514            c_int,
515            // `subsarray`, not a single subscript
516            *const ydb_buffer_t,
517            // ret_subs_used
518            *mut c_int,
519            // `ret_subsarray`, not a single subscript
520            *mut ydb_buffer_t,
521        ) -> c_int,
522    ) -> YDBResult<Vec<u8>> {
523        let subs_used = self.subscripts.len() as i32;
524        let mut err_buffer_t = Self::make_out_buffer_t(&mut err_buffer);
525
526        // this is a loop instead of a recursive call so we can keep the original `subs_used`
527        let (ret_subs_used, buffer_structs) = loop {
528            // Make sure `subscripts` is not a null pointer
529            if self.subscripts.capacity() == 0 {
530                self.subscripts.reserve(1);
531            }
532
533            // Get pointers to the varname and to the first subscript
534            let (varname, mut subscripts) = self.get_buffers_mut();
535            assert_eq!(subscripts.len(), self.subscripts.len());
536            let mut ret_subs_used = subscripts.len() as i32;
537
538            // Do the call
539            let status = unsafe {
540                c_func(
541                    tptoken.0,
542                    &mut err_buffer_t,
543                    &varname,
544                    subs_used,
545                    subscripts.as_ptr(),
546                    &mut ret_subs_used as *mut _,
547                    subscripts.as_mut_ptr(),
548                )
549            };
550            let ret_subs_used = ret_subs_used as usize;
551            // Handle resizing the buffer, if needed
552            // HACK: by reading the source, I saw that YDB will never return INVSTRLEN
553            // for the `err_buffer`, only for the subscripts (it will just not write as long a message).
554            // So it's ok for this to only look at the subscripts.
555            if status == YDB_ERR_INVSTRLEN {
556                let last_sub_index = ret_subs_used;
557                assert!(last_sub_index < self.subscripts.len());
558
559                let t = &mut self.subscripts[last_sub_index];
560                let needed_size = subscripts[last_sub_index].len_used as usize;
561                t.reserve(needed_size - t.len());
562                assert_ne!(t.as_ptr(), std::ptr::null());
563
564                continue;
565            }
566            if status == YDB_ERR_INSUFFSUBS {
567                self.subscripts.resize_with(ret_subs_used, || Vec::with_capacity(10));
568                continue;
569            }
570            // NOTE: `ret_subs_used` and `subscripts` came from references, so they can't be null.
571            if status == crate::craw::YDB_ERR_PARAMINVALID {
572                let i = ret_subs_used;
573                panic!(
574                    "internal error in node_prev_st: subscripts[{}] was null: {:?}",
575                    i, subscripts[i]
576                );
577            }
578            // Set length of the vec containing the buffer so we can see the value
579            if status != YDB_OK as i32 {
580                // We could end up with a buffer of a larger size if we couldn't fit the error string
581                // into the out_buffer, so make sure to pick the smaller size
582                let new_buffer_size = min(err_buffer_t.len_used, err_buffer_t.len_alloc) as usize;
583                unsafe {
584                    err_buffer.set_len(new_buffer_size);
585                }
586                // See https://gitlab.com/YottaDB/DB/YDB/-/issues/619
587                debug_assert_ne!(status, YDB_ERR_TPRETRY);
588                return Err(YDBError { message: err_buffer, status, tptoken });
589            }
590            break (ret_subs_used, subscripts);
591        };
592        assert!(
593            ret_subs_used <= self.subscripts.len(),
594            "growing the buffer should be handled in YDB_ERR_INSUFFSUBS (ydb {} > actual {})",
595            ret_subs_used,
596            self.subscripts.len()
597        );
598        self.subscripts.truncate(ret_subs_used);
599
600        // Update the length of each subscript
601        for (i, buff) in self.subscripts.iter_mut().enumerate() {
602            let actual = buffer_structs[i].len_used as usize;
603            unsafe {
604                buff.set_len(actual);
605            }
606        }
607
608        Ok(err_buffer)
609    }
610
611    /// Implements breadth-first traversal of a tree by searching for the next subscript.
612    ///
613    /// See also [KeyContext::next_sub](crate::context_api::KeyContext::next_sub).
614    #[inline]
615    pub(crate) fn sub_next_st(&self, tptoken: TpToken, out_buffer: Vec<u8>) -> YDBResult<Vec<u8>> {
616        self.direct_unsafe_call(tptoken, out_buffer, ydb_subscript_next_st)
617    }
618
619    /// Implements reverse breadth-first traversal of a tree by searching for the previous subscript.
620    ///
621    /// See also [KeyContext::prev_sub](crate::context_api::KeyContext::prev_sub).
622    #[inline]
623    pub(crate) fn sub_prev_st(&self, tptoken: TpToken, out_buffer: Vec<u8>) -> YDBResult<Vec<u8>> {
624        self.direct_unsafe_call(tptoken, out_buffer, ydb_subscript_previous_st)
625    }
626
627    // The Rust type system does not have a trait that means 'either a closure or an unsafe function'.
628    // This function creates the appropriate closure for a call to `non_allocating_ret_call`.
629    #[inline]
630    fn direct_unsafe_call(
631        &self, tptoken: TpToken, out_buffer: Vec<u8>,
632        func: unsafe extern "C" fn(
633            u64,
634            *mut ydb_buffer_t,
635            *const ydb_buffer_t,
636            i32,
637            *const ydb_buffer_t,
638            *mut ydb_buffer_t,
639        ) -> c_int,
640    ) -> YDBResult<Vec<u8>> {
641        let do_call = |tptoken, err_buffer_p, varname_p, len, subscripts_p, out_buffer_p| unsafe {
642            func(tptoken, err_buffer_p, varname_p, len, subscripts_p, out_buffer_p)
643        };
644        self.non_allocating_ret_call(tptoken, out_buffer, do_call)
645    }
646
647    /// Implements breadth-first traversal of a tree by searching for the next subscript, and passes itself in as the output parameter.
648    ///
649    /// `sub_next_self_st` can be written (less efficiently) using only safe code; see the `sub_next_equivalent` test.
650    ///
651    /// See also [KeyContext::next_sub_self](crate::context_api::KeyContext::next_sub_self).
652    pub(crate) fn sub_next_self_st(
653        &mut self, tptoken: TpToken, out_buffer: Vec<u8>,
654    ) -> YDBResult<Vec<u8>> {
655        let next_subscript = self.sub_next_st(tptoken, out_buffer)?;
656        // SAFETY: if there are no subscripts, `subscript_next` returns the next variable, which is always ASCII.
657        Ok(unsafe { self.replace_last_buffer(next_subscript) })
658    }
659
660    /// Implements reverse breadth-first traversal of a tree by searching for the previous subscript, and passes itself in as the output parameter.
661    ///
662    /// `sub_prev_self_st` can be written (less efficiently) using only safe code; see the `sub_prev_equivalent` test.
663    ///
664    /// See also [KeyContext::prev_sub_self](crate::context_api::KeyContext::prev_sub_self).
665    pub(crate) fn sub_prev_self_st(
666        &mut self, tptoken: TpToken, out_buffer: Vec<u8>,
667    ) -> YDBResult<Vec<u8>> {
668        let next_subscript = self.sub_prev_st(tptoken, out_buffer)?;
669        // SAFETY: if there are no subscripts, `subscript_prev` returns the next variable, which is always ASCII.
670        Ok(unsafe { self.replace_last_buffer(next_subscript) })
671    }
672
673    /// Replace the last subscript of this node with `next_subscript`, or replace the variable if
674    /// there are no subscripts.
675    ///
676    /// SAFETY: `next_subscript` must be valid UTF8 if there are no subscripts.
677    unsafe fn replace_last_buffer(&mut self, next_subscript: Vec<u8>) -> Vec<u8> {
678        let buffer = match self.last_mut() {
679            Some(subscr) => subscr,
680            None => self.variable.as_mut_vec(),
681        };
682        mem::replace(buffer, next_subscript)
683    }
684
685    fn make_out_buffer_t(out_buffer: &mut Vec<u8>) -> ydb_buffer_t {
686        if out_buffer.capacity() == 0 {
687            out_buffer.reserve(DEFAULT_CAPACITY);
688        }
689
690        ydb_buffer_t {
691            buf_addr: out_buffer.as_mut_ptr() as *mut _,
692            len_alloc: out_buffer.capacity() as u32,
693            len_used: out_buffer.len() as u32,
694        }
695    }
696
697    fn get_buffers_mut(&mut self) -> (ydb_buffer_t, Vec<ydb_buffer_t>) {
698        let var = ydb_buffer_t {
699            // `str::as_mut_ptr` was only stabilized in 1.36
700            buf_addr: unsafe { self.variable.as_bytes_mut() }.as_mut_ptr() as *mut _,
701            len_alloc: self.variable.capacity() as u32,
702            len_used: self.variable.len() as u32,
703        };
704        let iter = self.subscripts.iter_mut();
705        let subscripts = iter.map(Self::make_out_buffer_t).collect();
706        (var, subscripts)
707    }
708
709    /// Same as get_buffers_mut but takes `&self`
710    ///
711    /// NOTE: The pointers returned in the `ConstYDBBuffer`s _must never be modified_.
712    /// Doing so is immediate undefined behavior.
713    /// This is why `ConstYDBBuffer` was created,
714    /// so that modifying the private fields would be an error.
715    fn get_buffers(&self) -> (ConstYDBBuffer, Vec<ConstYDBBuffer>) {
716        let var = self.variable.as_bytes().into();
717        let subscripts = self.subscripts.iter().map(|vec| vec.as_slice().into()).collect();
718        (var, subscripts)
719    }
720}
721
722#[repr(transparent)]
723/// Because of this repr(transparent), it is safe to turn a
724/// `*const ConstYDBBuffer` to `*const ydb_buffer_t`
725struct ConstYDBBuffer<'a>(ydb_buffer_t, PhantomData<&'a [u8]>);
726
727impl ConstYDBBuffer<'_> {
728    fn as_ptr(&self) -> *const ydb_buffer_t {
729        &self.0
730    }
731}
732
733impl<'a> From<&'a [u8]> for ConstYDBBuffer<'a> {
734    fn from(slice: &[u8]) -> Self {
735        Self(
736            ydb_buffer_t {
737                buf_addr: slice.as_ptr() as *mut _,
738                len_used: slice.len() as u32,
739                len_alloc: slice.len() as u32,
740            },
741            PhantomData,
742        )
743    }
744}
745
746/// Allow Key to mostly be treated as a `Vec<Vec<u8>>` of subscripts,
747/// but without `shrink_to_fit`, `drain`, or other methods that aren't relevant.
748impl Key {
749    /// Remove all subscripts after the `i`th index.
750    ///
751    /// # See also
752    /// - [`Vec::truncate()`]
753    pub fn truncate(&mut self, i: usize) {
754        self.subscripts.truncate(i);
755    }
756    /// Remove all subscripts, leaving only the `variable`.
757    ///
758    /// # See also
759    /// - [`Vec::clear()`]
760    pub fn clear(&mut self) {
761        self.subscripts.clear();
762    }
763    /// Add a new subscript, keeping all existing subscripts in place.
764    ///
765    /// # See also
766    /// - [`Vec::push()`]
767    pub fn push(&mut self, subscript: Vec<u8>) {
768        self.subscripts.push(subscript);
769    }
770    /// Remove the last subscript, keeping all other subscripts in place.
771    ///
772    /// Note that this will _not_ return the `variable` even if there are no subscripts present.
773    ///
774    /// # See also
775    /// - [`Vec::pop()`]
776    pub fn pop(&mut self) -> Option<Vec<u8>> {
777        self.subscripts.pop()
778    }
779
780    /// Returns a mutable pointer to the last subscript, or `None` if there are no subscripts.
781    ///
782    /// # See also
783    /// - [`slice::last_mut()`](https://doc.rust-lang.org/std/primitive.slice.html#method.last_mut)
784    pub fn last_mut(&mut self) -> Option<&mut Vec<u8>> {
785        self.subscripts.last_mut()
786    }
787}
788
789/// `Key` has all the *immutable* functions of a `Vec` of subscripts.
790impl Deref for Key {
791    type Target = Vec<Vec<u8>>;
792
793    fn deref(&self) -> &Self::Target {
794        &self.subscripts
795    }
796}
797
798impl Index<usize> for Key {
799    type Output = Vec<u8>;
800
801    fn index(&self, i: usize) -> &Self::Output {
802        &self.subscripts[i]
803    }
804}
805
806impl IndexMut<usize> for Key {
807    fn index_mut(&mut self, i: usize) -> &mut Vec<u8> {
808        &mut self.subscripts[i]
809    }
810}
811
812impl<S: Into<String>> From<S> for Key {
813    fn from(s: S) -> Key {
814        Key::variable(s)
815    }
816}
817
818/// The status returned from a callback passed to [`Context::tp`]
819///
820/// [`Context::tp`]: crate::Context::tp
821#[derive(Debug, Copy, Clone)]
822pub enum TransactionStatus {
823    /// Complete the transaction and commit all changes
824    Ok = YDB_OK as isize,
825    /// Undo changes specified in `locals_to_reset`, then restart the transaction
826    Restart = YDB_TP_RESTART as isize,
827    /// Abort the transaction and undo all changes
828    Rollback = YDB_TP_ROLLBACK as isize,
829}
830
831type UserResult = Result<TransactionStatus, Box<dyn Error + Send + Sync>>;
832
833#[derive(Debug)]
834enum CallBackError {
835    // the callback returned an error
836    ApplicationError(Box<dyn Error + Send + Sync>),
837    // the callback panicked; this is the value `panic!` was called with
838    Panic(Box<dyn std::any::Any + Send + 'static>),
839}
840/// Passes the callback function as a structure to the callback
841struct CallBackStruct<'a> {
842    cb: &'a mut dyn FnMut(TpToken) -> UserResult,
843    /// Application error (not a YDBError)
844    error: Option<CallBackError>,
845}
846
847extern "C" fn fn_callback(tptoken: u64, _errstr: *mut ydb_buffer_t, tpfnparm: *mut c_void) -> i32 {
848    // We can't tell if the pointer is invalid (unallocated or already freed) but we can check it's not null and aligned.
849    // copied from https://github.com/rust-lang/rust/blob/84864bfea9c00fb90a1fa6e3af1d8ad52ce8f9ec/library/core/src/intrinsics.rs#L1742
850    fn is_aligned_and_not_null<T>(ptr: *const T) -> bool {
851        !ptr.is_null() && ptr as usize % std::mem::align_of::<T>() == 0
852    }
853    assert!(is_aligned_and_not_null(tpfnparm as *const CallBackStruct));
854    let callback_struct = unsafe { &mut *(tpfnparm as *mut CallBackStruct) };
855
856    let mut cb = panic::AssertUnwindSafe(&mut callback_struct.cb);
857    let tptoken = TpToken(tptoken);
858    // this deref_mut() is because Rust is having trouble with type inferrence
859    let retval = match panic::catch_unwind(move || cb.deref_mut()(tptoken)) {
860        Ok(val) => val,
861        Err(payload) => {
862            callback_struct.error = Some(CallBackError::Panic(payload));
863            return YDB_TP_ROLLBACK;
864        }
865    };
866    match retval {
867        Ok(status) => status as i32,
868        Err(err) => {
869            // Try to cast into YDBError; if we can do that, return the error code
870            // Else, rollback the transaction
871            // FIXME: it's possible for a downstream user to wrap a YDBError in their own error,
872            // in which case this may not return the right status.
873            let status = if let Some(ydb_err) = err.downcast_ref::<YDBError>() {
874                ydb_err.status
875            } else {
876                YDB_TP_ROLLBACK
877            };
878            callback_struct.error = Some(CallBackError::ApplicationError(err));
879            status
880        }
881    }
882}
883
884/// Start a new transaction, where `f` is the transaction to execute.
885///
886/// See also [Context::tp](crate::context_api::KeyContext::tp).
887pub(crate) fn tp_st<F>(
888    tptoken: TpToken, mut err_buffer: Vec<u8>, mut f: F, trans_id: &str, locals_to_reset: &[&str],
889) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>>
890where
891    F: FnMut(TpToken) -> UserResult,
892{
893    let mut err_buffer_t = Key::make_out_buffer_t(&mut err_buffer);
894
895    let mut locals: Vec<ConstYDBBuffer> = Vec::with_capacity(locals_to_reset.len());
896    for &local in locals_to_reset.iter() {
897        locals.push(local.as_bytes().into());
898    }
899    let locals_ptr = match locals.len() {
900        0 => ptr::null(),
901        _ => locals.as_ptr(),
902    };
903    let c_str = CString::new(trans_id).unwrap();
904    let mut callback_struct = CallBackStruct { cb: &mut f, error: None };
905    let arg = &mut callback_struct as *mut _ as *mut c_void;
906    let status = unsafe {
907        ydb_tp_st(
908            tptoken.into(),
909            &mut err_buffer_t,
910            Some(fn_callback),
911            arg,
912            c_str.as_ptr(),
913            locals.len() as i32,
914            locals_ptr as *const _,
915        )
916    };
917    if status as u32 == YDB_OK {
918        // there may be a possibility for an error to occur that gets caught
919        // and handled which appear to use the buffer even though none was
920        // returned at a high level.
921        unsafe {
922            err_buffer.set_len(min(err_buffer_t.len_used, err_buffer_t.len_alloc) as usize);
923        }
924        Ok(err_buffer)
925    } else if let Some(user_err) = callback_struct.error {
926        match user_err {
927            // an application error occurred; we _could_ return out_buffer if the types didn't conflict below
928            CallBackError::ApplicationError(mut err) => {
929                if let Some(ydb_err) = err.downcast_mut::<YDBError>() {
930                    // Use the outer tptoken, not the inner one. The inner transaction has already ended.
931                    ydb_err.tptoken = tptoken;
932                }
933                Err(err)
934            }
935            // reraise the panic now that we're past the FFI barrier
936            CallBackError::Panic(payload) => panic::resume_unwind(payload),
937        }
938    } else {
939        // a YDB error occurred; reuse out_buffer to return an error
940        unsafe {
941            err_buffer.set_len(min(err_buffer_t.len_used, err_buffer_t.len_alloc) as usize);
942        }
943        // See https://gitlab.com/YottaDB/DB/YDB/-/issues/619
944        debug_assert_ne!(status, YDB_ERR_TPRETRY);
945        Err(Box::new(YDBError { message: err_buffer, status, tptoken }))
946    }
947}
948
949/// Delete all local variables _except_ for those passed in `saved_variable`.
950///
951/// See also [Context::delete_excl](crate::context_api::Context::delete_excl).
952pub(crate) fn delete_excl_st(
953    tptoken: TpToken, err_buffer: Vec<u8>, saved_variables: &[&str],
954) -> YDBResult<Vec<u8>> {
955    use crate::craw::ydb_delete_excl_st;
956
957    let varnames: Vec<ConstYDBBuffer> =
958        saved_variables.iter().map(|var| var.as_bytes().into()).collect();
959
960    resize_call(tptoken, err_buffer, |tptoken, err_buffer_p| unsafe {
961        ydb_delete_excl_st(
962            tptoken,
963            err_buffer_p,
964            varnames.len() as c_int,
965            varnames.as_ptr() as *const _,
966        )
967    })
968}
969
970/// Given a binary sequence, serialize it to 'Zwrite format', which is ASCII printable.
971///
972/// See also [Context::str2zwr](crate::context_api::Context::str2zwr).
973pub(crate) fn str2zwr_st(
974    tptoken: TpToken, out_buf: Vec<u8>, original: &[u8],
975) -> YDBResult<Vec<u8>> {
976    use crate::craw::ydb_str2zwr_st;
977
978    let original_t = ConstYDBBuffer::from(original);
979    resize_ret_call(tptoken, out_buf, |tptoken, err_buffer_p, out_buffer_p| unsafe {
980        ydb_str2zwr_st(tptoken, err_buffer_p, original_t.as_ptr(), out_buffer_p)
981    })
982}
983
984/// Given a buffer in 'Zwrite format', deserialize it to the original binary buffer.
985///
986/// See also [Context::zwr2str](crate::context_api::Context::zwr2str).
987pub(crate) fn zwr2str_st(
988    tptoken: TpToken, out_buf: Vec<u8>, serialized: &[u8],
989) -> Result<Vec<u8>, YDBError> {
990    use crate::craw::ydb_zwr2str_st;
991
992    let serialized_t = ConstYDBBuffer::from(serialized);
993    resize_ret_call(tptoken, out_buf, |tptoken, err_buffer_p, out_buffer_p| unsafe {
994        ydb_zwr2str_st(tptoken, err_buffer_p, serialized_t.as_ptr(), out_buffer_p)
995    })
996}
997
998/// Write the message corresponding to a YottaDB error code to `out_buffer`.
999///
1000/// See also [Context::message](crate::context_api::Context::message).
1001pub(crate) fn message_t(tptoken: TpToken, out_buffer: Vec<u8>, status: i32) -> YDBResult<Vec<u8>> {
1002    resize_ret_call(tptoken, out_buffer, |tptoken, err_buffer_p, out_buffer_p| unsafe {
1003        ydb_message_t(tptoken, err_buffer_p, status, out_buffer_p)
1004    })
1005}
1006
1007/// Return a string in the format `rustwr <rust wrapper version> <$ZYRELEASE>`
1008///
1009/// See also [Context::release](crate::context_api::Context::release).
1010pub(crate) fn release_t(tptoken: TpToken, out_buffer: Vec<u8>) -> YDBResult<String> {
1011    let zrelease = Key::variable("$ZYRELEASE").get_st(tptoken, out_buffer)?;
1012    let zrelease = String::from_utf8(zrelease).expect("$ZRELEASE was not valid UTF8");
1013    Ok(format!("rustwr {} {}", env!("CARGO_PKG_VERSION"), zrelease))
1014}
1015
1016/// Runs the YottaDB deferred signal handler (if necessary).
1017///
1018/// See also [Context::eintr_handler](crate::context_api::Context::eintr_handler).
1019pub(crate) fn eintr_handler_t(tptoken: TpToken, err_buffer: Vec<u8>) -> YDBResult<Vec<u8>> {
1020    use crate::craw::ydb_eintr_handler_t;
1021
1022    resize_call(tptoken, err_buffer, |tptoken, err_buffer_p| unsafe {
1023        ydb_eintr_handler_t(tptoken, err_buffer_p)
1024    })
1025}
1026
1027/// Acquires locks specified in `locks` and releases all others.
1028///
1029/// See also [Context::lock](crate::context_api::Context::lock).
1030#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))]
1031pub(crate) fn lock_st(
1032    tptoken: TpToken, mut out_buffer: Vec<u8>, timeout: Duration, locks: &[Key],
1033) -> YDBResult<Vec<u8>> {
1034    use std::convert::TryFrom;
1035    use crate::craw::{
1036        YDB_ERR_MAXARGCNT, YDB_ERR_TIME2LONG, MAX_GPARAM_LIST_ARGS, ydb_lock_st,
1037        ydb_call_variadic_plist_func, gparam_list,
1038    };
1039    // `ydb_lock_st` is hard to work with because it uses variadic arguments.
1040    // The only way to pass a variable number of arguments in Rust is to pass a slice
1041    // or use a macro (i.e. know the number of arguments ahead of time).
1042
1043    // To get around this, `lock_st` calls the undocumented `ydb_call_variadic_plist_func` function.
1044    // `ydb_call_variadic_plist_func` takes the function to call, and a { len, args } struct.
1045    // `args` is a pointer to an array of length at most MAX_GPARAM_LIST_ARGS.
1046    // Under the covers, this effectively reimplements a user-land `va_list`,
1047    // turning a function that takes explicit number of parameters of a known type (`ydb_call_variadic_plist_func`)
1048    // into a variadic function call (`ydb_lock_st`).
1049
1050    // Each `arg` is typed as being a `void *`,
1051    // which means that on 32-bit platforms, 64-bit arguments have to be passed as 2 separate arguments.
1052    // This will hopefully be changed in YDB r1.32 to take a `uint64_t` instead.
1053
1054    // The function passed to `ydb_call_variadic_plist_func`
1055    // is typed as taking the number of arguments and then a variable number of args.
1056    // This is incorrect; any function can be passed to `ydb_call_variadic_plist_func`
1057    // as long as the parameters it takes match the arguments passed.
1058    // This is not planned to change in the near future.
1059
1060    let mut err_buffer_t = Key::make_out_buffer_t(&mut out_buffer);
1061    let keys: Vec<_> = locks.iter().map(|k| k.get_buffers()).collect();
1062
1063    type Void = *mut c_void;
1064    // Setup the initial args.
1065    // Note that all these arguments are required to have size of `void*`,
1066    // whatever that is on the target.
1067    let mut arg = [0 as Void; MAX_GPARAM_LIST_ARGS as usize];
1068    let mut i: usize;
1069
1070    // we can't just use `as usize` since on 32-bit platforms that will discard the upper half of the value
1071    // NOTE: this could be wrong if the pointer width is different from `usize`. We don't handle this case.
1072    let timeout_ns = match u64::try_from(timeout.as_nanos()) {
1073        Ok(n) => n,
1074        Err(_) => {
1075            // discard any previous error
1076            out_buffer.clear();
1077            return Err(YDBError { status: YDB_ERR_TIME2LONG, message: out_buffer, tptoken });
1078        }
1079    };
1080    #[cfg(target_pointer_width = "64")]
1081    {
1082        arg[0] = tptoken.0 as Void;
1083        arg[1] = &mut err_buffer_t as *mut _ as Void;
1084        arg[2] = timeout_ns as Void;
1085        arg[3] = keys.len() as Void;
1086        i = 4;
1087    }
1088    #[cfg(target_pointer_width = "32")]
1089    {
1090        let tptoken = tptoken.0;
1091        #[cfg(target_endian = "little")]
1092        {
1093            arg[0] = (tptoken & 0xffffffff) as Void;
1094            arg[1] = (tptoken >> 32) as Void;
1095            // arg[2] is err_buffer_t, but we need to use 2 slots for it to avoid unaligned accesses :(
1096            // here's hoping that YDB gets a better API upstream soon ...
1097            arg[4] = (timeout_ns & 0xffffffff) as Void;
1098            arg[5] = (timeout_ns >> 32) as Void;
1099        }
1100        #[cfg(target_endian = "big")]
1101        {
1102            let LOCK_ST_IS_UNTESTED_AND_UNSUPPORTED_ON_THIS_PLATFORM = ();
1103            arg[0] = (tptoken >> 32) as Void;
1104            arg[1] = (tptoken & 0xffffffff) as Void;
1105            arg[4] = (timeout_ns >> 32) as Void;
1106            arg[5] = (timeout_ns & 0xffffffff) as Void;
1107        }
1108        arg[2] = &mut err_buffer_t as *mut _ as Void;
1109        arg[6] = keys.len() as Void;
1110        i = 7;
1111    }
1112
1113    for (var, subscripts) in keys.iter() {
1114        if i + 2 >= MAX_GPARAM_LIST_ARGS as usize {
1115            return Err(YDBError {
1116                status: YDB_ERR_MAXARGCNT,
1117                message: format!("Expected at most {} arguments, got {}", MAX_GPARAM_LIST_ARGS, i)
1118                    .into_bytes(),
1119                tptoken,
1120            });
1121        }
1122        // we've already used some of the slots for the initial parameters,
1123        // so just keep a manual count instead of `enumerate`.
1124        arg[i] = var.as_ptr() as Void;
1125        arg[i + 1] = subscripts.len() as Void;
1126        arg[i + 2] = subscripts.as_ptr() as Void;
1127        i += 3;
1128    }
1129
1130    // In C, all pointers are mutable by default `const` is specified. Accordingly, `args` is
1131    // declared as mutable here, since it will be passed to C as the `cvplist` argument of
1132    // `ydb_call_variadic_plist_func()`, which is not declared as `const`.
1133    let mut args = gparam_list { n: i as isize, arg };
1134    let status = unsafe {
1135        // The types on `ydb_call_variadic_plist_func` are not correct. That is, `ydb_vplist_func` is defined as
1136        //      `typedef	uintptr_t	(*ydb_vplist_func)(uintptr_t cnt, ...)`,
1137        // but the type signature for `ydb_lock_st` is
1138        //      `int	ydb_lock_st(uint64_t tptoken, ydb_buffer_t *errstr, unsigned long long timeout_nsec, int namecount, ...);`.
1139        //
1140        // Additionally, `ydb_lock_st` on its own is a unique zero-sized-type (ZST):
1141        // See https://doc.rust-lang.org/reference/types/function-item.html#function-item-types for more details on function types
1142        // and https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts for details on ZSTs.
1143        // This `as *const ()` turns the unique ZST for `ydb_lock_st` into a proper function pointer.
1144        // Without the `as` cast, `transmute` will return `1_usize` and `ydb_call` will subsequently segfault.
1145        let ydb_lock_as_vplist_func = std::mem::transmute::<
1146            *const (),
1147            unsafe extern "C" fn() -> usize,
1148        >(ydb_lock_st as *const ());
1149        ydb_call_variadic_plist_func(Some(ydb_lock_as_vplist_func), &mut args as *mut gparam_list)
1150    };
1151
1152    // We could end up with a buffer of a larger size if we couldn't fit the error string
1153    // into the out_buffer, so make sure to pick the smaller size
1154    let len = min(err_buffer_t.len_used, err_buffer_t.len_alloc) as usize;
1155    unsafe {
1156        out_buffer.set_len(len);
1157    }
1158
1159    if status != YDB_OK as c_int {
1160        debug_assert_ne!(status, YDB_ERR_TPRETRY);
1161        Err(YDBError { message: out_buffer, status, tptoken })
1162    } else {
1163        Ok(out_buffer)
1164    }
1165}
1166
1167/// A call to the YDB API which only needs an `err_buffer`, not an `out_buffer`.
1168///
1169/// See documentation for [`non_allocating_call`] for more details.
1170///
1171/// `F: FnMut(tptoken, out_buffer_t) -> status`
1172#[doc(hidden)] // public for `ci_t!`
1173pub fn resize_call<F>(tptoken: TpToken, err_buffer: Vec<u8>, mut func: F) -> YDBResult<Vec<u8>>
1174where
1175    F: FnMut(u64, *mut ydb_buffer_t) -> c_int,
1176{
1177    resize_ret_call(tptoken, err_buffer, |tptoken, err_buffer_p, out_buffer_p| {
1178        let status = func(tptoken, err_buffer_p);
1179        unsafe {
1180            (*out_buffer_p).len_used = (*err_buffer_p).len_used;
1181        }
1182        status
1183    })
1184}
1185
1186/// A call to the YDB API which could need either an `err_buffer` or an `out_buffer`, but not both at once.
1187/// This uses the same underlying allocation (in `out_buffer`) for both.
1188///
1189/// See documentation for `non_allocating_ret_call` for more details.
1190///
1191/// `F: FnMut(tptoken, err_buffer_t, out_buffer_t) -> status`
1192fn resize_ret_call<F>(tptoken: TpToken, mut out_buffer: Vec<u8>, mut func: F) -> YDBResult<Vec<u8>>
1193where
1194    F: FnMut(u64, *mut ydb_buffer_t, *mut ydb_buffer_t) -> c_int,
1195{
1196    #[cfg(debug_assertions)]
1197    let mut i = 0;
1198
1199    let status = loop {
1200        let mut out_buffer_t = Key::make_out_buffer_t(&mut out_buffer);
1201        // NOTE: it is very important that this makes a copy of `out_buffer_t`:
1202        // otherwise, on `INVSTRLEN`, when YDB tries to set len_used it will overwrite the necessary
1203        // capacity with the length of the string.
1204        let mut err_buffer_t = out_buffer_t;
1205        // Do the call
1206        let status = func(tptoken.into(), &mut err_buffer_t, &mut out_buffer_t);
1207        // Handle resizing the buffer, if needed
1208        // The goal here is to catch any `INVSTRLEN` errors and resize the `out_buffer` to be large enough,
1209        // rather than forcing the end user to do it themselves.
1210        // However, in some edge cases, it's possible the `INVSTRLEN` will refer _not_ to `out_buffer`,
1211        // but to some other buffer that `func` passed in inside a nested call.
1212        // In particular, M FFI calls using `ci_t!` could accept ydb_string_t return types but not have enough memory allocated.
1213        // But there's no way to tell whether this is a call from `ci_t!` or not.
1214        // The trick this uses is to see if the existing `capacity` is already as long as `len_used`:
1215        // If so, the `INVSTRLEN` must be referring to a different buffer and there's nothing we can do about it.
1216        if status == YDB_ERR_INVSTRLEN {
1217            let len = out_buffer_t.len_used as usize;
1218            if out_buffer.capacity() >= len {
1219                return Err(YDBError { tptoken, message: out_buffer, status });
1220            }
1221            out_buffer.resize(len, 0);
1222            #[cfg(debug_assertions)]
1223            {
1224                i += 1;
1225                assert!(i <= 10, "possible infinite loop in `resize_ret_call`");
1226            }
1227            continue;
1228        }
1229        // Resize the vec with the buffer to we can see the value
1230        // We could end up with a buffer of a larger size if we couldn't fit the error string
1231        // into the out_buffer, so make sure to pick the smaller size
1232        let len = if status != YDB_OK as i32 {
1233            min(err_buffer_t.len_used, err_buffer_t.len_alloc)
1234        } else {
1235            min(out_buffer_t.len_used, out_buffer_t.len_alloc)
1236        };
1237        unsafe {
1238            out_buffer.set_len(len as usize);
1239        }
1240        break status;
1241    };
1242    if status != YDB_OK as i32 {
1243        debug_assert_ne!(status, YDB_ERR_TPRETRY);
1244        Err(YDBError { tptoken, message: out_buffer, status })
1245    } else {
1246        Ok(out_buffer)
1247    }
1248}
1249
1250#[cfg(test)]
1251pub(crate) mod tests;
1252
1253// Used for `compile_fail` tests
1254#[doc(hidden)]
1255#[cfg(doctest)]
1256/// Tests that `YDBError` cannot be constructed outside the `yottadb` crate.
1257/// ```compile_fail
1258/// use yottadb::*;
1259/// use yottadb::craw::YDB_OK;
1260/// let ydb_err = YDBError {
1261///   tptoken: YDB_NOTTP,
1262///   message: Vec::new(),
1263///   status: 0,
1264/// };
1265/// ```
1266pub fn used_for_doctests() {}