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() {}