Attribute Macro tarantool::proc

source · []
#[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)

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 return v
  • Err(e): the stored procedure will fail and e will be set as the last Tarantool error (see also TarantoolError::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 it’s any of it’s arguments to stderr and return immediately.