Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • core/tarantool-module
1 result
Show changes
Commits on Source (29)
Showing with 815 additions and 100 deletions
name: Synchronization
on: [push]
jobs:
synchronization:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.PUSH_PRIVATE_KEY }}
name: id_rsa
known_hosts: ${{ secrets.KNOWN_HOSTS }}
if_key_exists: replace
- name: Set remote url
run: git remote set-url --push origin git@git.picodata.io:picodata/picodata/tarantool-module.git
- name: Push files
run: git push
/target
Cargo.lock
/.idea/
......@@ -4,7 +4,7 @@ stages:
- lint
variables:
DOCKER_IMAGE: registry.gitlab.com/picodata/dockers/brod-builder:0.5
DOCKER_IMAGE: docker-public.binary.picodata.io/brod-builder:0.5
cache:
key:
......@@ -54,3 +54,4 @@ pages:
artifacts:
paths:
- public
# Change Log
# [0.6.0] Mar 17 2022
Added
### Added
- `tlua::Lua::new_thread` & `tarantool::lua_state`
- ability to set a custom filter for tarantool logger (see
`TarantoolLogger::with_mapping`)
- `AsTuple` implementation for longer tuples
- `CString` & `CStr` support in **tlua**
- `update_space` macro for operations of different types
- `tlua::TableFromIter`
- `FunctionCtx::as_struct` for streamlined conversions
- `std::fmt::Debug` implementations for `Tuple`, `TupleBuffer` & others
- `is_nullable` setting for space field formats
- `tlua::error` macro for throwing a lua error from a rust callback
- `LuaError::WrongType` is returned if a rust callback receives incorrect
arguments
- `LuaRead` implementation for `[T; N]`
- space/index creation builder api
- specifying index parts by json path
- `fiber::Mutex`
- `Indexable`, `IndexableRW` & `Callable` types for working with generic (not
just builtin ones) indexable & callable lua values
- `AsLua::pcall` for calling rust functions in protected mode capturing any
lua exceptions
### Changed
- join handles returned by `fiber::`{`start`|`defer`}[`_proc`] now have a
lifetime parameter, which allows non-static fiber functions
- conversions between `Tuple`, `TupleBuffer` and `Vec<u8>` have been reorganized
and made safer
- reading `Vec<T>` from lua no longer ignores elements that failed to convert to
`T`
- some `tarantool::tuple` operation signatures have been changed removing
unnecessary `Result`s, whenever an error cannot actually happen
- `fiber::Channel` only accepts `'static` values now
### Fixed
- `tlua::push_userdata` no longer requires arguments implement `Send`
- partially pushed tuples poluting the stack
- assertion violation when trying to create a tuple with incorrect msgpack data
- build for Arm MacOS
# [0.5.1] Dec 24 2021
**TODO**
[workspace]
members = [
"tarantool",
"tarantool-proc",
"tlua",
"tlua-derive",
"tests",
......
......@@ -61,7 +61,7 @@ rustflags = [
Add the following lines to your project Cargo.toml:
```toml
[dependencies]
tarantool = "0.5"
tarantool = "0.6"
[lib]
crate-type = ["cdylib"]
......@@ -114,7 +114,7 @@ edition = "2018"
# author, license, etc
[dependencies]
tarantool = "0.5.0"
tarantool = "0.6.0"
serde = "1.0"
[lib]
......@@ -211,7 +211,7 @@ Create a new crate "harder". Put these lines to `lib.rs`:
```rust
use serde::{Deserialize, Serialize};
use std::os::raw::c_int;
use tarantool_module::tuple::{AsTuple, FunctionArgs, FunctionCtx, Tuple};
use tarantool::tuple::{AsTuple, FunctionArgs, FunctionCtx, Tuple};
#[derive(Serialize, Deserialize)]
struct Args {
......@@ -246,7 +246,7 @@ passable_table = {}
table.insert(passable_table, 1)
table.insert(passable_table, 2)
table.insert(passable_table, 3)
capi_connection:call('harder', passable_table)
capi_connection:call('harder', {passable_table})
```
This time the call is passing a Lua table (`passable_table`) to the `harder()` function. The `harder()` function will see
......@@ -254,7 +254,7 @@ it, it's in the char `args` parameter.
And now the screen looks like this:
```
tarantool> capi_connection:call('harder', passable_table)
tarantool> capi_connection:call('harder', {passable_table})
field_count = 3
val=1
val=2
......@@ -274,8 +274,8 @@ use std::os::raw::c_int;
use serde::{Deserialize, Serialize};
use tarantool_module::space::Space;
use tarantool_module::tuple::{AsTuple, FunctionArgs, FunctionCtx};
use tarantool::space::Space;
use tarantool::tuple::{AsTuple, FunctionArgs, FunctionCtx};
#[derive(Serialize, Deserialize)]
struct Row {
......@@ -294,7 +294,7 @@ pub extern "C" fn hardest(ctx: FunctionCtx, _: FunctionArgs) -> c_int {
str_field: "String 2".to_string(),
}
);
ctx.return_tuple(result.unwrap().unwrap()).unwrap()
ctx.return_tuple(&result.unwrap().unwrap()).unwrap()
}
```
This time the rust function is doing three things:
......@@ -336,8 +336,8 @@ use std::os::raw::c_int;
use serde::{Deserialize, Serialize};
use tarantool_module::space::Space;
use tarantool_module::tuple::{AsTuple, FunctionArgs, FunctionCtx};
use tarantool::space::Space;
use tarantool::tuple::{AsTuple, FunctionArgs, FunctionCtx};
#[derive(Serialize, Deserialize, Debug)]
struct Row {
......@@ -395,36 +395,31 @@ Create a new crate "write". Put these lines to `lib.rs`:
```rust
use std::os::raw::c_int;
use tarantool_module::error::{set_error, Error, TarantoolErrorCode};
use tarantool_module::fiber::sleep;
use tarantool_module::space::Space;
use tarantool_module::transaction::start_transaction;
use tarantool_module::tuple::{FunctionArgs, FunctionCtx};
use tarantool::error::{Error, TarantoolErrorCode};
use tarantool::fiber::sleep;
use tarantool::space::Space;
use tarantool::transaction::start_transaction;
use tarantool::tuple::{FunctionArgs, FunctionCtx};
#[no_mangle]
pub extern "C" fn hardest(ctx: FunctionCtx, _: FunctionArgs) -> c_int {
let mut space = match Space::find("capi_test").unwrap() { // (1)
None => {
return set_error(
file!(),
line!(),
&TarantoolErrorCode::ProcC,
"Can't find space capi_test",
)
}
Some(space) => space,
};
let row = (1, 22); // (2)
start_transaction(|| -> Result<(), Error> { // (3)
space.replace(&row, false)?; // (4)
Ok(()) // (5)
})
.unwrap();
sleep(0.001);
ctx.return_mp(&row).unwrap() // (6)
pub extern "C" fn write(ctx: FunctionCtx, _: FunctionArgs) -> c_int {
let mut space = match Space::find("capi_test") {
None => {
return tarantool::set_error!(TarantoolErrorCode::ProcC, "Can't find space capi_test")
}
Some(space) => space,
};
let row = (1, "22".to_string());
start_transaction(|| -> Result<(), Error> {
space.replace(&row)?;
Ok(())
})
.unwrap();
sleep(std::time::Duration::from_millis(1));
ctx.return_mp(&row).unwrap()
}
```
1. once again, finding the `capi_test` space by calling `Space::find_by_name()`;
......
[package]
authors = [
"Georgy Moshkin <gmoshkin@picodata.io>",
]
name = "tarantool-proc"
description = "Tarantool proc macros"
version = "0.1.0"
edition = "2021"
license = "BSD-2-Clause"
documentation = "https://docs.rs/tarantool-proc/"
repository = "https://github.com/picodata/tarantool-module"
[lib]
proc-macro = true
test = false
doctest = false
[dependencies]
syn = { version = "^1.0", features = [ "full" ] }
quote = "^1.0"
proc-macro2 = "^1.0"
use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2};
use syn::{
AttributeArgs, parse_macro_input, FnArg, Generics, Item, ItemFn,
Lit, Meta, MetaNameValue, NestedMeta, PatType, Signature,
};
use quote::quote;
#[proc_macro_attribute]
pub fn stored_proc(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as AttributeArgs);
let Context {
tarantool,
debug_tuple,
is_packed,
..
} = Context::from_args(args);
let input = parse_macro_input!(item as Item);
let ItemFn { sig, block, .. } = match input {
Item::Fn(f) => f,
_ => panic!("only `fn` items can be stored procedures"),
};
let (ident, inputs, output) = match sig {
Signature { asyncness: Some(_), .. } => {
panic!("async stored procedures are not supported yet")
}
Signature { generics: Generics { lt_token: Some(_), .. }, .. } => {
panic!("generic stored procedures are not supported yet")
}
Signature { variadic: Some(_), .. } => {
panic!("variadic stored procedures are not supported yet")
}
Signature { ident, inputs, output, .. } => (ident, inputs, output),
};
if is_packed && inputs.len() > 1 {
panic!("proc with 'packed_args' can only have a single parameter")
}
let input_idents = inputs.iter()
.map(|i| match i {
FnArg::Receiver(_) => {
panic!("`self` receivers aren't supported in stored procedures")
}
FnArg::Typed(PatType { pat, .. }) => pat,
})
.collect::<Vec<_>>();
let input_pattern = if inputs.is_empty() {
quote!{ []: [(); 0] }
} else if is_packed {
quote!{ #(#input_idents)* }
} else {
quote!{ ( #(#input_idents,)* ) }
};
quote! {
#[no_mangle]
pub unsafe extern "C" fn #ident (
__tp_ctx: #tarantool::tuple::FunctionCtx,
__tp_args: #tarantool::tuple::FunctionArgs,
) -> ::std::os::raw::c_int {
let __tp_tuple = #tarantool::tuple::Tuple::from(__tp_args);
#debug_tuple
let #input_pattern =
match __tp_tuple.into_struct() {
::std::result::Result::Ok(__tp_args) => __tp_args,
::std::result::Result::Err(__tp_err) => {
#tarantool::set_error!(
#tarantool::error::TarantoolErrorCode::ProcC,
"{}",
__tp_err
);
return -1;
}
};
fn __tp_inner(#inputs) #output {
#block
}
let __tp_res = __tp_inner(#(#input_idents),*);
#tarantool::proc::Return::ret(__tp_res, __tp_ctx)
}
}.into()
}
struct Context {
tarantool: TokenStream2,
debug_tuple: TokenStream2,
is_packed: bool,
}
impl Context {
fn from_args(args: AttributeArgs) -> Self {
let mut tarantool = quote! { ::tarantool };
let mut debug_tuple = quote! {};
let mut is_packed = false;
for arg in args {
match arg {
NestedMeta::Lit(lit) => {
eprintln!("unsuported attribute argument: {:?}", lit)
}
NestedMeta::Meta(Meta::Path(path))
if path.get_ident()
.map(|p| p == "packed_args")
.unwrap_or(false) => {
is_packed = true
}
NestedMeta::Meta(Meta::Path(path))
if path.get_ident()
.map(|p| p == "debug")
.unwrap_or(false) => {
debug_tuple = quote! {
let __tp_tuple = ::std::dbg!(__tp_tuple);
}
}
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit,
..
})) if path.get_ident()
.map(|p| p == "tarantool")
.unwrap_or(false) => {
match &lit {
Lit::Str(s) => {
let tp: syn::Path = imp::parse_lit_str(s).unwrap();
tarantool = quote! { #tp };
}
_ => panic!("tarantool value must be a string literal"),
}
}
NestedMeta::Meta(meta) => {
eprintln!("unsuported attribute argument: {:?}", meta)
}
}
}
Self {
tarantool,
debug_tuple,
is_packed,
}
}
}
mod imp {
use proc_macro2::{Group, Span, TokenStream, TokenTree};
use syn::parse::{self, Parse};
// stolen from serde
pub(crate) fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T>
where
T: Parse,
{
let tokens = spanned_tokens(s)?;
syn::parse2(tokens)
}
fn spanned_tokens(s: &syn::LitStr) -> parse::Result<TokenStream> {
let stream = syn::parse_str(&s.value())?;
Ok(respan(stream, s.span()))
}
fn respan(stream: TokenStream, span: Span) -> TokenStream {
stream
.into_iter()
.map(|token| respan_token(token, span))
.collect()
}
fn respan_token(mut token: TokenTree, span: Span) -> TokenTree {
if let TokenTree::Group(g) = &mut token {
*g = Group::new(g.delimiter(), respan(g.stream(), span));
}
token.set_span(span);
token
}
}
[package]
name = "tarantool"
description = "Tarantool rust bindings"
version = "0.6.0"
version = "0.6.1"
authors = [
"Dmitriy Koltsov <dkoltsov@picodata.io>",
"Georgy Moshkin <gmoshkin@picodata.io>",
......@@ -40,6 +40,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_bytes = "^0"
sha-1 = "0.9"
tarantool-proc = { path = "../tarantool-proc", version = "0.1.0" }
uuid = "0.8.2"
[target.'cfg(not(all(target_arch = "aarch64", target_os = "macos")))'.dependencies]
......
......@@ -584,7 +584,7 @@ impl serde::Serialize for Decimal {
S: serde::Serializer,
{
#[derive(Serialize)]
struct _ExtStruct((i8, serde_bytes::ByteBuf));
struct _ExtStruct((std::os::raw::c_char, serde_bytes::ByteBuf));
let data = unsafe {
let len = ffi::decimal_len(&self.inner) as usize;
......@@ -604,12 +604,12 @@ impl<'de> serde::Deserialize<'de> for Decimal {
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct _ExtStruct((i8, serde_bytes::ByteBuf));
struct _ExtStruct((std::os::raw::c_char, serde_bytes::ByteBuf));
match serde::Deserialize::deserialize(deserializer)? {
_ExtStruct((ffi::MP_DECIMAL, bytes)) => {
let data = bytes.into_vec();
let data_p = &mut data.as_ptr().cast::<i8>();
let data_p = &mut data.as_ptr().cast();
let mut dec = std::mem::MaybeUninit::uninit();
let res = unsafe {
ffi::decimal_unpack(data_p, data.len() as _, dec.as_mut_ptr())
......
......@@ -15,7 +15,7 @@ extern "C" {
pub const DECNUMUNITS: u32 = 13;
pub const DECIMAL_MAX_DIGITS: u32 = 38;
pub const MP_DECIMAL: i8 = 1;
pub const MP_DECIMAL: c_char = 1;
extern "C" {
/// Return decimal precision,
......
pub const MP_UUID: i8 = 2;
pub const MP_UUID: std::os::raw::c_char = 2;
#[repr(C)]
#[derive(Copy, Clone)]
......
......@@ -109,6 +109,7 @@ macro_rules! define_setters {
impl<'a> Builder<'a> {
/// Creates a new index builder with default options.
#[inline(always)]
pub fn new(space_id: u32, name: &'a str) -> Self {
Self {
space_id,
......@@ -134,14 +135,16 @@ impl<'a> Builder<'a> {
func(func: String)
}
pub fn part(mut self, part: Part) -> Self {
#[inline(always)]
pub fn part(mut self, part: impl Into<Part>) -> Self {
self.opts.parts.get_or_insert_with(|| Vec::with_capacity(8))
.push(part);
.push(part.into());
self
}
/// Create a new index using the current options.
#[cfg(feature = "schema")]
#[inline(always)]
pub fn create(self) -> crate::Result<Index> {
crate::schema::index::create_index(self.space_id, self.name, &self.opts)
}
......@@ -349,6 +352,52 @@ impl Part {
}
}
impl From<&str> for Part {
#[inline(always)]
fn from(f: &str) -> Self {
Self::field(f.to_string())
}
}
impl From<String> for Part {
#[inline(always)]
fn from(f: String) -> Self {
Self::field(f)
}
}
impl From<u32> for Part {
#[inline(always)]
fn from(f: u32) -> Self {
Self::field(f)
}
}
impl From<(u32, IndexFieldType)> for Part {
#[inline(always)]
fn from((f, t): (u32, IndexFieldType)) -> Self {
Self::field(f).field_type(t)
}
}
impl From<(String, IndexFieldType)> for Part {
#[inline(always)]
fn from((f, t): (String, IndexFieldType)) -> Self {
Self::field(f).field_type(t)
}
}
impl From<(&str, IndexFieldType)> for Part {
#[inline(always)]
fn from((f, t): (&str, IndexFieldType)) -> Self {
Self::field(f.to_string()).field_type(t)
}
}
////////////////////////////////////////////////////////////////////////////////
// ...
////////////////////////////////////////////////////////////////////////////////
/// Type of distance for retree index.
#[derive(Copy, Clone, Debug, Serialize, tlua::Push)]
pub enum RtreeIndexDistanceType {
......@@ -466,14 +515,7 @@ impl Index {
K: AsTuple,
Op: AsTuple,
{
let mp_encoded_ops = ops.iter().try_fold(
Vec::with_capacity(ops.len()),
|mut v, op| -> crate::Result<Vec<Vec<u8>>> {
let buf = rmp_serde::to_vec(&op)?;
v.push(buf);
Ok(v)
})?;
let mp_encoded_ops = Self::encode_ops(ops)?;
self.update_mp(key, &mp_encoded_ops)
}
......@@ -514,10 +556,21 @@ impl Index {
where
T: AsTuple,
Op: AsTuple,
{
let mp_encoded_ops = Self::encode_ops(ops)?;
self.upsert_mp(value, &mp_encoded_ops)
}
pub fn upsert_mp<T>(&mut self, value: &T, ops: &[Vec<u8>]) -> Result<(), Error>
where
T: AsTuple,
{
let value_buf = value.serialize_as_tuple().unwrap();
let value_buf_ptr = value_buf.as_ptr() as *const c_char;
let ops_buf = ops.serialize_as_tuple().unwrap();
let mut buf = Vec::with_capacity(128);
rmp::encode::write_array_len(&mut buf, ops.len() as u32)?;
ops.iter().try_for_each(|op_buf| buf.write_all(op_buf))?;
let ops_buf = unsafe { TupleBuffer::from_vec_unchecked(buf) };
let ops_buf_ptr = ops_buf.as_ptr() as *const c_char;
tuple_from_box_api!(
ffi::box_upsert[
......@@ -530,9 +583,18 @@ impl Index {
0,
@out
]
)
.map(|t| if t.is_some() {
unreachable!("Upsert doesn't return a tuple")
).map(|t| if t.is_some() {
unreachable!("Upsert doesn't return a tuple")
})
}
fn encode_ops<Op: AsTuple>(ops: &[Op]) -> crate::Result<Vec<Vec<u8>>> {
ops.iter().try_fold(
Vec::with_capacity(ops.len()),
|mut v, op| -> crate::Result<Vec<Vec<u8>>> {
let buf = rmp_serde::to_vec(&op)?;
v.push(buf);
Ok(v)
})
}
......
......@@ -11,6 +11,7 @@
//! - [Decimal numbers](mod@decimal)
//! - [Logging](log) (see <https://docs.rs/log/>)
//! - [Error handling](error)
//! - [Stored procedures](macro@crate::proc)
//!
//! > **Caution!** The library is currently under development.
//! > API may be unstable until version 1.0 will be released.
......@@ -168,6 +169,7 @@ pub mod fiber;
pub mod index;
pub mod log;
pub mod net_box;
pub mod proc;
pub mod raft;
pub mod schema;
pub mod sequence;
......@@ -182,6 +184,99 @@ pub mod uuid;
mod va_list;
pub use tlua;
/// `#[tarantool::proc]` macro attribute for creating stored procedure
/// functions.
///
/// ```rust
/// #[tarantool::proc]
/// fn add(x: i32, y: i32) -> i32 {
/// x + y
/// }
/// ```
///
/// From tarantool create a "C" stored procedure and call with arguments wrapped
/// within a lua table:
/// ```lua
/// box.schema.func.create("libname.add", { language = 'C' })
/// assert(box.func['libname.add']:call({ 1, 2 }) == 3)
/// ```
///
/// # Returning errors
///
/// If a function's return type is [`Result`]`<T, E>` (where `E` implements
/// [`Display`]), then if it's return value is
/// - `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`])
/// ```rust
/// use tarantool::{error::TarantoolError, index::IteratorType::Eq, space::Space};
///
/// #[tarantool::proc]
/// fn get_name(id: usize) -> Result<Option<String>, TarantoolError> {
/// 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
///
/// Stored procedure's return type must implement the [`Return`] trait which is
/// implemented for most builtin types. To retun an arbitrary type that
/// implements [`serde::Serialize`] you can use the [`ReturnMsgpack`] wrapper
/// type.
///
/// # Packed arguments
///
/// By default the stored procedure unpacks the received tuple and assigns the
/// **i**th field of the tuple to the **i**th argument. And if the number of
/// arguments is less then the number of fields in the input tuple the rest are
/// ignored.
///
/// If you want to instead deserialize the tuple directly into your structure
/// you can use the `packed_args`
/// attribute parameter
/// ```rust
/// #[tarantool::proc(packed_args)]
/// fn sum_all(vals: Vec<i32>) -> i32 {
/// vals.sum()
/// }
///
/// #[tarantool::proc]
/// fn sum_first_3(a: i32, b: i32, c: i32) -> String {
/// 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
///
/// # 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.
///
/// [`Result`]: std::result::Result
/// [`Display`]: std::fmt::Display
/// [`TarantoolError::last`]: crate::error::TarantoolError::last
/// [`Return`]: crate::proc::Return
/// [`ReturnMsgpack`]: crate::proc::ReturnMsgpack
pub use tarantool_proc::stored_proc as proc;
/// Return a global tarantool lua state.
///
......
use crate::{
error::TarantoolErrorCode::ProcC,
set_error,
tuple::FunctionCtx,
};
use serde::Serialize;
use std::{
fmt::Display,
os::raw::c_int,
};
////////////////////////////////////////////////////////////////////////////////
// ReturnMsgpack
////////////////////////////////////////////////////////////////////////////////
/// A wrapper type for returning custom types form stored procedures.
///
/// # using `ReturnMsgpack` directly
///
/// You can either return `ReturnMsgpack` directly:
///
/// ```
/// use tarantool::proc::ReturnMsgpack;
///
/// #[tarantool::proc]
/// fn foo(x: i32) -> ReturnMsgpack<MyStruct> {
/// ReturnMsgpack(MyStruct { x, y: x * 2 })
/// }
///
/// #[derive(Serialize)]
/// struct MyStruct { x: i32, y: i32 }
/// ```
///
/// # implementing `Return` for custom type
///
/// Or you can use it to implement `Return` for your custom type:
///
/// ```
/// use std::os::raw::c_int;
/// use tarantool::{proc::{Return, ReturnMsgpack}, tuple::FunctionCtx};
///
/// #[tarantool::proc]
/// fn foo(x: i32) -> MyStruct {
/// MyStruct { x, y: x * 2 }
/// }
///
/// #[derive(Serialize)]
/// struct MyStruct { x: i32, y: i32 }
///
/// impl Return for MyStruct {
/// fn ret(self, ctx: FunctionCtx) -> c_int {
/// ReturnMsgpack(self).ret(ctx)
/// }
/// }
/// ```
pub struct ReturnMsgpack<T>(pub T);
impl<T: Serialize> Return for ReturnMsgpack<T> {
#[inline(always)]
fn ret(self, ctx: FunctionCtx) -> c_int {
return_mp(self.0, ctx)
}
}
#[inline(always)]
fn return_mp(v: impl Serialize, ctx: FunctionCtx) -> c_int {
match ctx.return_mp(&v) {
Ok(_) => 0,
Err(e) => {
set_error!(ProcC, "{}", e);
-1
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Return
////////////////////////////////////////////////////////////////////////////////
pub trait Return: Sized {
fn ret(self, ctx: FunctionCtx) -> c_int;
}
impl Return for () {
#[inline(always)]
fn ret(self, _: FunctionCtx) -> c_int {
0
}
}
impl<O, E> Return for Result<O, E>
where
O: Serialize,
E: Display,
{
#[inline(always)]
fn ret(self, ctx: FunctionCtx) -> c_int {
match self {
Ok(o) => match ctx.return_mp(&o) {
Ok(_) => 0,
Err(e) => {
set_error!(ProcC, "{}", e);
-1
}
}
Err(e) => {
set_error!(ProcC, "{}", e);
-1
}
}
}
}
macro_rules! impl_return {
(impl $([ $( $tp:tt )* ])? for $t:ty) => {
impl $(< $($tp)* >)? Return for $t
where
Self: Serialize,
{
#[inline(always)]
fn ret(self, ctx: FunctionCtx) -> c_int {
return_mp(self, ctx)
}
}
};
($( $t:ty )+) => {
$( impl_return!{ impl for $t } )+
}
}
impl_return!{ impl[V] for Option<V> }
impl_return!{ impl[V] for Vec<V> }
impl_return!{ impl[V] for &'_ [V] }
impl_return!{ impl[V, const N: usize] for [V; N] }
impl_return!{ impl[K, V] for std::collections::HashMap<K, V> }
impl_return!{ impl[K] for std::collections::HashSet<K> }
impl_return!{ impl[K, V] for std::collections::BTreeMap<K, V> }
impl_return!{ impl[K] for std::collections::BTreeSet<K> }
impl_return!{
bool
i8 u8 i16 u16 i32 u32 i64 u64 i128 u128 isize usize
f32 f64
String &'_ str
std::ffi::CString &'_ std::ffi::CStr
}
macro_rules! impl_return_for_tuple {
() => {};
($h:ident $($t:ident)*) => {
impl<$h, $($t),*> Return for ($h, $($t,)*)
where
Self: Serialize,
{
#[inline(always)]
fn ret(self, ctx: FunctionCtx) -> c_int {
return_mp(self, ctx)
}
}
impl_return_for_tuple!{$($t)*}
}
}
impl_return_for_tuple!{A B C D E F G H I J K L M N O P Q}
......@@ -596,6 +596,20 @@ impl Space {
{
self.primary_key().upsert(value, ops)
}
/// Upsert a tuple using `ops` already encoded in message pack format.
///
/// This function is similar to [`upsert`](#method.upsert) but instead
/// of a generic type parameter `Op` it accepts preencoded message pack
/// values. This is usefull when the operations have values of different
/// types.
#[inline(always)]
pub fn upsert_mp<K>(&mut self, key: &K, ops: &[Vec<u8>]) -> Result<(), Error>
where
K: AsTuple,
{
self.primary_key().upsert_mp(key, ops)
}
}
////////////////////////////////////////////////////////////////////////////////
......@@ -684,3 +698,31 @@ macro_rules! update {
}
};
}
/// Upsert a tuple or index.
///
/// The helper macro with semantic same as `space.upsert()`/`index.upsert()` functions, but supports
/// different types in `ops` argument.
///
/// - `target` - updated space or index.
/// - `value` - encoded tuple in MsgPack Array format (`[part1, part2, ...]`).
/// - `ops` - encoded operations in MsgPack array format, e.g. `[['=', field_id, 100], ['!', 2, 'xxx']]`
///
/// See also: [space.update()](#method.update)
#[macro_export]
macro_rules! upsert {
($target:expr, $value: expr, $($op:expr),+ $(,)?) => {
{
use std::borrow::Borrow;
let mut f = || -> $crate::Result<()> {
let ops = [
$(
$crate::util::rmp_to_vec($op.borrow())?,
)+
];
$target.upsert_mp($value.borrow(), &ops)
};
f()
}
};
}
use crate::ffi::uuid as ffi;
use std::os::raw::c_char;
use serde::{Serialize, Deserialize};
pub use ::uuid::{
......@@ -217,7 +218,7 @@ impl serde::Serialize for Uuid {
S: serde::Serializer,
{
#[derive(Serialize)]
struct _ExtStruct((i8, serde_bytes::ByteBuf));
struct _ExtStruct((c_char, serde_bytes::ByteBuf));
let data = self.as_bytes();
_ExtStruct((ffi::MP_UUID, serde_bytes::ByteBuf::from(data as &[_])))
......@@ -231,7 +232,7 @@ impl<'de> serde::Deserialize<'de> for Uuid {
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct _ExtStruct((i8, serde_bytes::ByteBuf));
struct _ExtStruct((c_char, serde_bytes::ByteBuf));
let _ExtStruct((kind, bytes)) = serde::Deserialize::deserialize(deserializer)?;
......
......@@ -17,11 +17,15 @@ serde_json = "1.0"
tester = "0.7.0"
once_cell = "1.9.0"
rmp-serde = "1"
rmpv = { version = "1", features = ["with-serde"] }
[dependencies.tarantool]
path = "../tarantool"
features = ["all"]
[dependencies.tarantool-proc]
path = "../tarantool-proc"
[lib]
test = false
crate-type = ["cdylib"]
use rand::Rng;
use tarantool::index::{IndexOptions, IteratorType};
use tarantool::index::{IndexFieldType, IndexOptions, IteratorType};
use tarantool::sequence::Sequence;
use tarantool::space::{Space, SpaceCreateOptions, SystemSpace};
use tarantool::tuple::Tuple;
use tarantool::update;
use tarantool::{update, upsert};
use crate::common::{QueryOperation, S1Record, S2Key, S2Record};
pub fn test_space_get_by_name() {
pub fn space_get_by_name() {
assert!(Space::find("test_s1").is_some());
assert!(Space::find("test_s1_invalid").is_none());
}
pub fn test_space_get_system() {
pub fn space_get_system() {
let space: Space = SystemSpace::Space.into();
assert!(space.len().is_ok());
}
pub fn test_index_get_by_name() {
pub fn index_get_by_name() {
let space = Space::find("test_s2").unwrap();
assert!(space.index("idx_1").is_some());
assert!(space.index("idx_1_invalid").is_none());
}
pub fn test_box_get() {
pub fn get() {
let space = Space::find("test_s2").unwrap();
let idx_1 = space.index("idx_1").unwrap();
......@@ -56,7 +56,7 @@ pub fn test_box_get() {
);
}
pub fn test_box_select() {
pub fn select() {
let space = Space::find("test_s2").unwrap();
let result: Vec<S1Record> = space
.primary_key()
......@@ -131,7 +131,7 @@ pub fn test_box_select() {
);
}
pub fn test_box_select_composite_key() {
pub fn select_composite_key() {
let space = Space::find("test_s2").unwrap();
let idx = space.index("idx_2").unwrap();
......@@ -152,12 +152,12 @@ pub fn test_box_select_composite_key() {
);
}
pub fn test_box_len() {
pub fn len() {
let space = Space::find("test_s2").unwrap();
assert_eq!(space.len().unwrap(), 20_usize);
}
pub fn test_box_random() {
pub fn random() {
let space = Space::find("test_s2").unwrap();
let idx = space.primary_key();
let mut rng = rand::thread_rng();
......@@ -172,7 +172,7 @@ pub fn test_box_random() {
assert_eq!(output.value, format!("value_{}", output.id));
}
pub fn test_box_min_max() {
pub fn min_max() {
let space = Space::find("test_s2").unwrap();
let idx = space.index("idx_3").unwrap();
......@@ -203,7 +203,7 @@ pub fn test_box_min_max() {
);
}
pub fn test_box_count() {
pub fn count() {
let space = Space::find("test_s2").unwrap();
assert_eq!(
space.primary_key().count(IteratorType::LE, &(7,),).unwrap(),
......@@ -215,7 +215,7 @@ pub fn test_box_count() {
);
}
pub fn test_box_extract_key() {
pub fn extract_key() {
let space = Space::find("test_s2").unwrap();
let idx = space.index("idx_2").unwrap();
let record = S2Record {
......@@ -233,7 +233,7 @@ pub fn test_box_extract_key() {
);
}
pub fn test_box_insert() {
pub fn insert() {
let mut space = Space::find("test_s1").unwrap();
space.truncate().unwrap();
......@@ -252,7 +252,7 @@ pub fn test_box_insert() {
assert_eq!(output.unwrap().into_struct::<S1Record>().unwrap(), input);
}
pub fn test_box_replace() {
pub fn replace() {
let mut space = Space::find("test_s1").unwrap();
space.truncate().unwrap();
......@@ -280,7 +280,7 @@ pub fn test_box_replace() {
);
}
pub fn test_box_delete() {
pub fn delete() {
let mut space = Space::find("test_s1").unwrap();
space.truncate().unwrap();
......@@ -301,7 +301,7 @@ pub fn test_box_delete() {
assert!(output.is_none());
}
pub fn test_box_update() {
pub fn update() {
let mut space = Space::find("test_s1").unwrap();
space.truncate().unwrap();
......@@ -338,7 +338,7 @@ pub fn test_box_update() {
);
}
pub fn test_box_update_macro() {
pub fn update_macro() {
let mut space = Space::find("test_s2").unwrap();
let input = S2Record {
......@@ -378,7 +378,7 @@ pub fn test_box_update_macro() {
assert_eq!(output.b, 2);
}
pub fn test_box_update_index_macro() {
pub fn update_index_macro() {
let mut space = Space::find("test_s2").unwrap();
let input = S2Record {
......@@ -424,7 +424,7 @@ pub fn test_box_update_index_macro() {
assert_eq!(output.b, 2);
}
pub fn test_box_upsert() {
pub fn upsert() {
let mut space = Space::find("test_s1").unwrap();
space.truncate().unwrap();
......@@ -475,7 +475,57 @@ pub fn test_box_upsert() {
);
}
pub fn test_box_truncate() {
pub fn upsert_macro() {
let mut space = Space::find("test_s2").unwrap();
let original_input = S2Record {
id: 111,
key: "test_box_upsert_macro_1".to_string(),
value: "Original".to_string(),
a: 0,
b: 0,
};
space.insert(&original_input).unwrap();
let () = upsert!(
space,
&(S2Record {
id: 111,
key: "does not matter".to_string(),
value: "UpsertNew".to_string(),
a: 2,
b: 2
}),
("=", "value", "UpsertUpdated"),
("=", "a", 1),
)
.unwrap();
let () = upsert!(
space,
&S2Record {
id: 112,
key: "test_box_upsert_macro_2".to_string(),
value: "UpsertNew".to_string(),
a: 2,
b: 2
},
("=", "key", "UpsertUpdated"),
("=", "a", 1),
).unwrap();
let output = space.get(&(111, )).unwrap().unwrap().into_struct::<S2Record>().unwrap();
assert_eq!(output.key, "test_box_upsert_macro_1");
assert_eq!(output.value, "UpsertUpdated");
assert_eq!(output.a, 1);
let output = space.get(&(112, )).unwrap().unwrap().into_struct::<S2Record>().unwrap();
assert_eq!(output.key, "test_box_upsert_macro_2");
assert_eq!(output.value, "UpsertNew");
assert_eq!(output.a, 2);
}
pub fn truncate() {
let mut space = Space::find("test_s1").unwrap();
space.truncate().unwrap();
......@@ -493,19 +543,19 @@ pub fn test_box_truncate() {
assert_eq!(space.len().unwrap(), 0_usize);
}
pub fn test_box_sequence_get_by_name() {
pub fn sequence_get_by_name() {
assert!(Sequence::find("test_seq").unwrap().is_some());
assert!(Sequence::find("test_seq_invalid").unwrap().is_none());
}
pub fn test_box_sequence_iterate() {
pub fn sequence_iterate() {
let mut seq = Sequence::find("test_seq").unwrap().unwrap();
seq.reset().unwrap();
assert_eq!(seq.next().unwrap(), 1);
assert_eq!(seq.next().unwrap(), 2);
}
pub fn test_box_sequence_set() {
pub fn sequence_set() {
let mut seq = Sequence::find("test_seq").unwrap().unwrap();
seq.reset().unwrap();
assert_eq!(seq.next().unwrap(), 1);
......@@ -514,7 +564,7 @@ pub fn test_box_sequence_set() {
assert_eq!(seq.next().unwrap(), 100);
}
pub fn test_space_create_opt_default() {
pub fn space_create_opt_default() {
let opts = SpaceCreateOptions::default();
// Create space with default options.
......@@ -524,7 +574,7 @@ pub fn test_space_create_opt_default() {
drop_space("new_space_1");
}
pub fn test_space_create_opt_if_not_exists() {
pub fn space_create_opt_if_not_exists() {
let mut opts = SpaceCreateOptions::default();
let _result = Space::create("new_space_2", &opts);
......@@ -540,7 +590,7 @@ pub fn test_space_create_opt_if_not_exists() {
drop_space("new_space_2");
}
pub fn test_space_create_id_increment() {
pub fn space_create_id_increment() {
let opts = SpaceCreateOptions::default();
let _result = Space::create("new_space_3", &opts);
let mut prev_id = Space::find("new_space_3").unwrap().id();
......@@ -560,7 +610,7 @@ pub fn test_space_create_id_increment() {
}
#[allow(clippy::field_reassign_with_default)]
pub fn test_space_create_opt_user() {
pub fn space_create_opt_user() {
let mut opts = SpaceCreateOptions::default();
// Test `user` option.
......@@ -576,7 +626,7 @@ pub fn test_space_create_opt_user() {
drop_space("new_space_4");
}
pub fn test_space_create_opt_id() {
pub fn space_create_opt_id() {
let opts = SpaceCreateOptions {
id: Some(10000),
.. Default::default()
......@@ -589,7 +639,7 @@ pub fn test_space_create_opt_id() {
drop_space("new_space_6");
}
pub fn test_space_drop() {
pub fn space_drop() {
let opts = SpaceCreateOptions::default();
for i in 400..406 {
......@@ -603,7 +653,7 @@ pub fn test_space_drop() {
}
}
pub fn test_index_create_drop() {
pub fn index_create_drop() {
let space_opts = SpaceCreateOptions::default();
let space = Space::create("new_space_7", &space_opts).unwrap();
......@@ -631,3 +681,27 @@ pub fn drop_space(name: &str) {
let result = Space::find(name).unwrap().drop();
assert_eq!(result.is_err(), false);
}
pub fn index_parts() {
let mut space = Space::builder("index_parts_test")
.create().unwrap();
let index = space.index_builder("pk")
.part((1, IndexFieldType::Unsigned))
.part(2)
.create().unwrap();
space.insert(&(1, 2, 3)).unwrap();
space.insert(&(2, "foo")).unwrap();
space.insert(&(3, 3.14, [3, 2, 1])).unwrap();
space.insert(&(4,)).unwrap_err();
space.insert(&("5", 1)).unwrap_err();
let mut iter = index.select(tarantool::index::IteratorType::All, &())
.unwrap();
assert_eq!(iter.next().and_then(|t| t.as_struct().ok()), Some((1, 2, 3)));
assert_eq!(iter.next().and_then(|t| t.as_struct().ok()), Some((2, "foo".to_string())));
assert_eq!(iter.next().and_then(|t| t.as_struct().ok()), Some((3, 3.14, [3, 2, 1])));
assert!(iter.next().is_none());
}
......@@ -5,10 +5,10 @@ use std::os::unix::io::{AsRawFd, FromRawFd};
use std::os::unix::net::UnixStream;
use std::time::Duration;
use tarantool::coio::{channel, coio_call, CoIOListener, CoIOStream, Receiver, Sender};
use tarantool::coio::{channel, self, CoIOListener, CoIOStream, Receiver, Sender};
use tarantool::fiber::{sleep, Fiber};
pub fn test_coio_accept() {
pub fn coio_accept() {
let tcp_listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = tcp_listener.local_addr().unwrap();
......@@ -23,7 +23,7 @@ pub fn test_coio_accept() {
assert!(accept_result.is_ok());
}
pub fn test_coio_read_write() {
pub fn coio_read_write() {
let (reader_soc, writer_soc) = UnixStream::pair().unwrap();
let mut reader_fiber = Fiber::new("test_fiber", &mut move |soc: Box<UnixStream>| {
......@@ -50,8 +50,8 @@ pub fn test_coio_read_write() {
writer_fiber.join();
}
pub fn test_coio_call() {
let res = coio_call(
pub fn coio_call() {
let res = coio::coio_call(
&mut |x| {
assert_eq!(*x, 99);
100
......@@ -61,7 +61,7 @@ pub fn test_coio_call() {
assert_eq!(res, 100)
}
pub fn test_channel() {
pub fn coio_channel() {
let (tx, rx) = channel::<i32>(1);
let mut fiber_a = Fiber::new("test_fiber_a", &mut |tx: Box<Sender<i32>>| {
......@@ -83,7 +83,7 @@ pub fn test_channel() {
fiber_b.join();
}
pub fn test_channel_rx_closed() {
pub fn channel_rx_closed() {
let (tx, _) = channel::<i32>(1);
let mut fiber = Fiber::new("test_fiber", &mut |tx: Box<Sender<i32>>| {
......@@ -95,7 +95,7 @@ pub fn test_channel_rx_closed() {
fiber.join();
}
pub fn test_channel_tx_closed() {
pub fn channel_tx_closed() {
let (_, rx) = channel::<i32>(1);
let mut fiber = Fiber::new("test_fiber", &mut |rx: Box<Receiver<i32>>| {
......