#[proc]
Expand description
#[tarantool::proc]
is a macro attribute for creating stored procedure
functions.
#[tarantool::proc]
fn add(x: i32, y: i32) -> i32 {
x + y
}
Create a “C” stored procedure from Tarantool and call it with arguments wrapped within a Lua table:
box.schema.func.create("libname.add", { language = 'C' })
assert(box.func['libname.add']:call({ 1, 2 }) == 3)
Collecting stored procedures
All stored procs defined with #[tarantool::proc]
attribute are
automatically added to a global array and can be accessed via
proc::all_procs
function.
use tarantool::proc::all_procs;
#[tarantool::proc]
fn my_proc() -> i32 { 69 }
let procs = all_procs();
assert_eq!(procs[0].name(), "my_proc");
This can be used to generate stored procedure defintions for tarantool’s
box.schema.func.create
. Although there’s currently no easy way to fully
automate this process, because of how loading dynamic modules works in
tarantool. To be able to access the list of procs from a module you need to
call a function defined in that module.
See how you can bootstrap proc definitions in example in examples/all_procs
.
NOTE: collecting stored procedures is implemented by inserting the
pointers to the functions into a static array. This can sometimes cause
problems when functions marked #[tarantool::proc]
are defined in the same
mod as unit tests marked #[::test]
, specifically on MacOS. This is most
likely due to a bug in the compiler, but until it is fixed we’ve added a
workaround, such that procs are added to the global array under the
#[cfg(not(test))]
attribute. This can only affect you, if you try
collecting stored procedures from the rust builtin unit tests, which there’s
no reason for you to do. If you want to test collecting of stored procedures,
consider using #[tarantool::test]
.
Accepting borrowed arguments
It can sometimes be more efficient to borrow the procedure’s arguments rather than copying them. This usecase is supported, however it is not entirely safe. Due to how stored procedures are implemented in tarantool, the arguments are allocated in a volatile region of memory, which can be overwritten by some tarantool operations. Therefore you cannot rely on the borrowed arguments being valid for the lifetime of the procedure call.
This proc is safe, because the data is accessed before any other calls to tarantool api:
#[tarantool::proc]
fn strlen(s: &str) -> usize {
s.len()
}
This one however is unsafe:
use tarantool::{error::Error, index::IteratorType::Eq, space::Space};
use std::collections::HashSet;
#[tarantool::proc]
fn count_common_friends(user1: &str, user2: String) -> Result<usize, Error> {
// A call to tarantool api.
let space = Space::find("friends_with").unwrap();
// This call is unsafe, because borrowed data `user1` is accessed
// after a call to tarantool api.
let iter = space.select(Eq, &[user1])?;
let user1_friends: HashSet<String> = iter
.map(|tuple| tuple.get(1).unwrap())
.collect();
// This call is safe, because `user2` is owned.
let iter = space.select(Eq, &[user2])?;
let user2_friends: HashSet<String> = iter
.map(|tuple| tuple.get(1).unwrap())
.collect();
Ok(user1_friends.intersection(&user2_friends).count())
}
Returning errors
Assuming the function’s return type is Result
<T, E>
(where E
implements
Display
), the return values read as follows:
Ok(v)
: the stored procedure will returnv
Err(e)
: the stored procedure will fail ande
will be set as the last Tarantool error (see alsoTarantoolError::last
)
use tarantool::{error::Error, index::IteratorType::Eq, space::Space};
#[tarantool::proc]
fn get_name(id: usize) -> Result<Option<String>, Error> {
Ok(
if let Some(space) = Space::find("users") {
if let Some(row) = space.select(Eq, &[id])?.next() {
row.get("name")
} else {
None
}
} else {
None
}
)
}
Returning custom types
The return type of the stored procedure must implement the Return
trait which is
implemented for most built-in types. To return an arbitrary type that
implements serde::Serialize
you can use the ReturnMsgpack
wrapper
type or the custom_ret
attribute parameter.
#[derive(serde::Serialize)]
struct Complex {
re: f64,
im: f64,
}
#[tarantool::proc(custom_ret)]
fn sqrt(x: f64) -> Complex {
if x < 0. {
Complex { re: 0., im: x.abs().sqrt() }
} else {
Complex { re: x.sqrt(), im: 0. }
}
}
// above is equivalent to this
use tarantool::proc::ReturnMsgpack;
#[tarantool::proc]
fn sqrt_explicit(x: f64) -> ReturnMsgpack<Complex> {
ReturnMsgpack(
if x < 0. {
Complex { re: 0., im: x.abs().sqrt() }
} else {
Complex { re: x.sqrt(), im: 0. }
}
)
}
Packed arguments
By default the stored procedure unpacks the received tuple and assigns the ith field of the tuple to the ith argument. If there are fewer arguments than there are fields in the input tuple, the unused tuple fields are ignored.
If you want to instead deserialize the tuple directly into your structure
you can use the packed_args
attribute parameter
#[tarantool::proc(packed_args)]
fn sum_all(vals: Vec<i32>) -> i32 {
vals.into_iter().sum()
}
#[tarantool::proc]
fn sum_first_3(a: i32, b: i32, c: i32) -> i32 {
a + b + c
}
In the above example sum_all
will sum all the inputs values it received
whereas sum_first_3
will only sum up the first 3 values
Injecting arguments
Because the return value of the stored procedure is immediately serialized it is in theory ok to return borrowed values. Rust however will not allow you to return references to the values owned by the function. In that case you can use an injected argument, which will be created just outside the stored procedure and will be passed to it as a corresponding argument.
fn global_data() -> &'static [String] {
todo!()
}
#[tarantool::proc]
fn get_ith<'a>(
#[inject(global_data())]
data: &'a [String],
i: usize,
) -> &'a str {
&data[i]
}
When calling the stored procedure only the actual arguments need to be
specified, so in the above example get_ith
will effectively have just 1
argument i
. And data
will be automatically injected and it’s value will
be set to global_data()
each time it is called.
Debugging
There’s also a debug
attribute parameter which enables debug printing of
the arguments received by the stored procedure
#[tarantool::proc(debug)]
fn print_what_you_got() {}
The above stored procedure will just print any of it’s arguments to stderr and return immediately.
Create a tarantool stored procedure.
See tarantool::proc
doc-comments in tarantool crate for details.