├── .gitignore ├── .gitlab-ci.yml ├── .gitlab ├── issue_templates │ └── bug_report.md └── merge_request_templates │ └── default.md ├── AUTHORS.MD ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── docker ├── ci-log-section ├── picodata.Dockerfile └── vanilla.Dockerfile ├── examples ├── all_procs │ ├── Cargo.toml │ └── src │ │ ├── init.lua │ │ └── lib.rs ├── async-h1-client │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── init.lua │ │ └── lib.rs ├── easy │ ├── Cargo.toml │ ├── init.lua │ └── src │ │ └── lib.rs ├── harder │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── hardest │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── luaopen │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── read │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── tokio-hyper │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── init.lua │ │ └── lib.rs └── write │ ├── Cargo.toml │ └── src │ └── lib.rs ├── perf-test ├── Cargo.toml ├── run.lua └── src │ └── lib.rs ├── tarantool-proc ├── Cargo.toml └── src │ ├── lib.rs │ └── test.rs ├── tarantool ├── Cargo.toml └── src │ ├── access_control.rs │ ├── auth.rs │ ├── cbus │ ├── mod.rs │ ├── oneshot.rs │ ├── sync │ │ ├── mod.rs │ │ ├── std.rs │ │ └── tokio.rs │ └── unbounded.rs │ ├── clock.rs │ ├── coio.rs │ ├── datetime.rs │ ├── decimal.rs │ ├── define_str_enum.rs │ ├── error.rs │ ├── ffi │ ├── datetime.rs │ ├── decimal.rs │ ├── helper.rs │ ├── mod.rs │ ├── sql.rs │ ├── tarantool.rs │ └── uuid.rs │ ├── fiber.rs │ ├── fiber │ ├── async.rs │ ├── async │ │ ├── mutex.rs │ │ ├── oneshot.rs │ │ ├── timeout.rs │ │ └── watch.rs │ ├── channel.rs │ ├── csw.rs │ ├── mutex.rs │ └── safety.rs │ ├── index.rs │ ├── lib.rs │ ├── log.rs │ ├── msgpack.rs │ ├── msgpack │ └── encode.rs │ ├── net_box │ ├── index.rs │ ├── inner.rs │ ├── mod.rs │ ├── options.rs │ ├── promise.rs │ ├── recv_queue.rs │ ├── schema.rs │ ├── send_queue.rs │ ├── space.rs │ └── stream.rs │ ├── network │ ├── client │ │ ├── mod.rs │ │ ├── reconnect.rs │ │ ├── stream.rs │ │ ├── tcp.rs │ │ └── tls.rs │ ├── mod.rs │ └── protocol │ │ ├── api.rs │ │ ├── codec.rs │ │ └── mod.rs │ ├── proc.rs │ ├── read_view.rs │ ├── schema │ ├── function.rs │ ├── index.rs │ ├── mod.rs │ ├── sequence.rs │ └── space.rs │ ├── sequence.rs │ ├── session.rs │ ├── space.rs │ ├── sql.rs │ ├── test.rs │ ├── time.rs │ ├── transaction.rs │ ├── trigger.rs │ ├── tuple.rs │ ├── util.rs │ ├── uuid.rs │ └── vclock.rs ├── tests ├── Cargo.toml ├── README.md ├── run.rs ├── run_tests.lua └── src │ ├── access_control.rs │ ├── auth.rs │ ├── bench_bulk_insert.lua │ ├── bench_server.lua │ ├── box.rs │ ├── coio.rs │ ├── common.rs │ ├── datetime.rs │ ├── define_str_enum.rs │ ├── enums.rs │ ├── fiber │ ├── channel.rs │ ├── mod.rs │ ├── mutex.rs │ └── old.rs │ ├── latch.rs │ ├── lib.rs │ ├── net_box.rs │ ├── proc.rs │ ├── session.rs │ ├── sql.rs │ ├── test_attr.rs │ ├── tlua │ ├── any.rs │ ├── functions_write.rs │ ├── lua_functions.rs │ ├── lua_tables.rs │ ├── misc.rs │ ├── mod.rs │ ├── object.rs │ ├── rust_tables.rs │ ├── userdata.rs │ └── values.rs │ ├── transaction.rs │ ├── tuple.rs │ ├── tuple_picodata.rs │ └── uuid.rs ├── tlua-derive ├── Cargo.toml └── src │ └── lib.rs └── tlua ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── any.rs ├── cdata.rs ├── debug.rs ├── ffi.rs ├── functions_write.rs ├── lib.rs ├── lua_functions.rs ├── lua_tables.rs ├── macros.rs ├── object.rs ├── rust_tables.rs ├── test.rs ├── tuples.rs ├── userdata.rs ├── util.rs └── values.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /.idea/ 4 | .vscode 5 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/bug_report.md: -------------------------------------------------------------------------------- 1 | ## Environment 2 | Release / Commit: 3 | 4 | OS: 5 | 6 | Rust version: 7 | 8 | ## Steps to Reproduce 9 | 10 | ## Expected Behavior 11 | 12 | ## Actual Behavior 13 | 14 | ## (Optional) Additional Information 15 | 16 | ## (Optional) Suggested Actions -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/default.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | %{all_commits} 4 | 5 | ## Ensure that 6 | 7 | - [ ] New code is covered by tests 8 | - [ ] API is documented 9 | - [ ] Changelog is up to date 10 | - [ ] Version is bumped in the appropriate `Cargo.toml` files 11 | -------------------------------------------------------------------------------- /AUTHORS.MD: -------------------------------------------------------------------------------- 1 | Roman Tsisyk roman@tsisyk.com 2 | Anton Melnikov volt0@picodata.io 3 | Dmitriy Koltsov dkoltsov@picodata.io 4 | Georgy Moshkin gmoshkin@picodata.io 5 | Egor Ivkov e.ivkov@picodata.io -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "1" 3 | members = [ 4 | "tarantool", 5 | "tarantool-proc", 6 | "tlua", 7 | "tlua-derive", 8 | "tests", 9 | "examples/all_procs", 10 | "examples/easy", 11 | "examples/harder", 12 | "examples/hardest", 13 | "examples/read", 14 | "examples/write", 15 | "examples/tokio-hyper", 16 | "examples/async-h1-client", 17 | "examples/luaopen", 18 | "perf-test" 19 | ] 20 | 21 | [profile.dev] 22 | lto = "thin" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, AUTHORS: please see AUTHORS file 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 19 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cargo build --all 3 | 4 | test: 5 | cargo build -p tarantool-module-test-runner 6 | cargo test 7 | 8 | test-pd: 9 | cargo build -p tarantool-module-test-runner --features=picodata,tokio_components 10 | TARANTOOL_EXECUTABLE=tarantool-pd cargo test 11 | 12 | bench: 13 | cargo build -p perf-test --release 14 | TARANTOOL_MODULE_BUILD_MODE="release" tarantool perf-test/run.lua 15 | -------------------------------------------------------------------------------- /docker/ci-log-section: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | usage() { 4 | echo "Usage:" 5 | echo " ci-log-section start SECTION_NAME SECTION_HEADER" 6 | echo " ci-log-section end SECTION_NAME" 7 | exit -1 8 | } 9 | 10 | # See: https://docs.gitlab.com/ee/ci/jobs/#custom-collapsible-sections 11 | 12 | if [ "$1" == "start" ]; then 13 | [ -n "$2" ] || usage 14 | echo -en "\e[0K" 15 | echo -en "section_start:$(date +%s):$2[collapsed=true]" 16 | echo -en "\r\e[0K" 17 | echo "${@:3}" 18 | elif [ "$1" == "end" ]; then 19 | [ -n "$2" ] || usage 20 | echo -en "\e[0K" 21 | echo -en "section_end:$(date +%s):$2" 22 | echo -en "\r\e[0K" 23 | echo 24 | else 25 | usage 26 | fi 27 | -------------------------------------------------------------------------------- /docker/picodata.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG TARANTOOL_TAG 2 | FROM docker-public.binary.picodata.io/tarantool:${TARANTOOL_TAG} 3 | ARG RUST_VERSION 4 | 5 | RUN set -e; \ 6 | rm -f /etc/yum.repos.d/pg.repo && \ 7 | yum -y install gcc git openssl-devel && \ 8 | yum clean all 9 | 10 | # Install rust + cargo 11 | ENV PATH=/root/.cargo/bin:${PATH} 12 | RUN set -e; \ 13 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |\ 14 | sh -s -- -y --profile default --default-toolchain ${RUST_VERSION} -c rustfmt -c clippy; 15 | 16 | # Install glauth for LDAP testing 17 | RUN set -e; \ 18 | cd /bin; \ 19 | curl -L -o glauth https://github.com/glauth/glauth/releases/download/v2.3.0/glauth-linux-amd64; \ 20 | chmod +x glauth; 21 | 22 | COPY docker/ci-log-section /usr/bin/ci-log-section 23 | -------------------------------------------------------------------------------- /docker/vanilla.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | ARG RUST_VERSION 3 | 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | RUN apt update && apt install -y curl; 7 | 8 | RUN curl -L https://tarantool.io/release/2/installer.sh | bash; 9 | 10 | RUN apt install -y \ 11 | gcc \ 12 | git \ 13 | libssl-dev \ 14 | pkg-config \ 15 | tarantool \ 16 | tarantool-dev \ 17 | ; 18 | 19 | ENV PATH=/root/.cargo/bin:${PATH} 20 | RUN set -e; \ 21 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |\ 22 | sh -s -- -y --profile default --default-toolchain ${RUST_VERSION} -c rustfmt -c clippy; 23 | 24 | COPY docker/ci-log-section /usr/bin/ci-log-section 25 | -------------------------------------------------------------------------------- /examples/all_procs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "all_procs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "BSD-2-Clause" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies.tarantool] 10 | path = "../../tarantool" 11 | features = ["stored_procs_slice"] 12 | 13 | [lib] 14 | test = false 15 | crate-type = ["cdylib"] 16 | -------------------------------------------------------------------------------- /examples/all_procs/src/init.lua: -------------------------------------------------------------------------------- 1 | box.cfg() 2 | 3 | -- You need to provide the module name and the name of that first proc. 4 | -- Don't forget to provide a path to `liball_procs.so` in LUA_CPATH env var. 5 | box.schema.func.create('all_procs.proc_names', { language = 'C' }) 6 | 7 | -- After that you can use 'all_procs.proc_names' to define the rest of the procs 8 | local all_procs = box.func['all_procs.proc_names']:call() 9 | for _, proc in pairs(all_procs) do 10 | box.schema.func.create( 11 | ('all_procs.%s'):format(proc), { language = 'C', if_not_exists = true } 12 | ) 13 | end 14 | 15 | -- Now all the procs are defined 16 | assert(box.func['all_procs.hello']:call() == 'hello') 17 | assert(box.func['all_procs.add']:call{1, 2} == 3) 18 | 19 | os.exit(0) 20 | -------------------------------------------------------------------------------- /examples/all_procs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[tarantool::proc] 2 | fn proc_names() -> Vec<&'static str> { 3 | tarantool::proc::all_procs() 4 | .iter() 5 | .map(|p| p.name()) 6 | .collect() 7 | } 8 | 9 | #[tarantool::proc] 10 | fn hello() -> &'static str { 11 | "hello" 12 | } 13 | 14 | #[tarantool::proc] 15 | fn add(a: i32, b: i32) -> i32 { 16 | a + b 17 | } 18 | -------------------------------------------------------------------------------- /examples/async-h1-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-h1-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "BSD-2-Clause" 6 | 7 | [dependencies] 8 | tarantool = { path = "../../tarantool" } 9 | http-types = "2.9.0" 10 | async-h1 = "2.3.3" 11 | async-native-tls = { version = "0.5.0", optional=true } 12 | 13 | [features] 14 | tls = ["async-native-tls"] 15 | 16 | [lib] 17 | test = false 18 | crate-type = ["cdylib"] 19 | -------------------------------------------------------------------------------- /examples/async-h1-client/README.md: -------------------------------------------------------------------------------- 1 | # Example async_h1 HTTP client 2 | 3 | This is a simple example of how you can use **tarantool** with an 4 | **async_h1** based async http client running on our built-in fiber based runtime. 5 | 6 | Build the code: 7 | ```bash 8 | cargo build -p async-h1-client 9 | ``` 10 | 11 | _(Optional)_ Build the code with TLS support, to make requests also via HTTPS: 12 | ```bash 13 | cargo build -p async-h1-client --features tls 14 | ``` 15 | _For this to work OpenSSL has to be installed in the system._ 16 | 17 | Make sure `LUA_CPATH` environment variable is setup correctly, e.g. if your 18 | target director is `./target`: 19 | ```bash 20 | export LUA_CPATH='./target/debug/lib?.so' 21 | ``` 22 | 23 | Start tarantool with the provided entry script: 24 | ```bash 25 | tarantool examples/async-h1-client/src/init.lua 26 | ``` 27 | 28 | Send a `GET` request to any url: 29 | ```bash 30 | tarantool> box.func['async_h1_client.get']:call({"http://example.org"}) 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/async-h1-client/src/init.lua: -------------------------------------------------------------------------------- 1 | box.cfg({listen = 3301}) 2 | 3 | box.schema.func.create('async_h1_client.get', {language = 'C', if_not_exists = true}) 4 | box.schema.user.grant('guest', 'execute', 'function', 'async_h1_client.get', {if_not_exists = true}) 5 | 6 | require 'console'.start() 7 | -------------------------------------------------------------------------------- /examples/async-h1-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | use http_types::{Method, Request, Url}; 2 | use tarantool::error::Error; 3 | use tarantool::fiber; 4 | use tarantool::network::client::tcp::TcpStream; 5 | use tarantool::proc; 6 | 7 | #[proc] 8 | fn get(url: &str) -> Result<(), Error> { 9 | fiber::block_on(async { 10 | println!("Connecting..."); 11 | let url = Url::parse(url).map_err(Error::other)?; 12 | let host = url 13 | .host_str() 14 | .ok_or_else(|| Error::other("host not specified"))?; 15 | let req = Request::new(Method::Get, url.clone()); 16 | let mut res = match url.scheme() { 17 | "http" => { 18 | let stream = TcpStream::connect(host, 80).map_err(Error::other)?; 19 | println!("Sending request over http..."); 20 | async_h1::connect(stream, req).await.map_err(Error::other)? 21 | } 22 | #[cfg(feature = "tls")] 23 | "https" => { 24 | let stream = TcpStream::connect(host, 443).map_err(Error::other)?; 25 | let stream = async_native_tls::connect(host, stream) 26 | .await 27 | .map_err(Error::other)?; 28 | println!("Sending request over https..."); 29 | async_h1::connect(stream, req).await.map_err(Error::other)? 30 | } 31 | _ => { 32 | return Err(Error::other("scheme not supported")); 33 | } 34 | }; 35 | 36 | println!("Response Status: {}", res.status()); 37 | println!( 38 | "Response Body: {}", 39 | res.body_string().await.map_err(Error::other)? 40 | ); 41 | Ok(()) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /examples/easy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "easy" 3 | version = "0.1.0" 4 | authors = [ 5 | "Dmitriy Koltsov ", 6 | "Anton Melnikov " 7 | ] 8 | edition = "2018" 9 | license = "BSD-2-Clause" 10 | 11 | [dependencies.tarantool] 12 | path = "../../tarantool" 13 | 14 | [lib] 15 | test = false 16 | crate-type = ["cdylib"] 17 | -------------------------------------------------------------------------------- /examples/easy/init.lua: -------------------------------------------------------------------------------- 1 | box.cfg({listen = 3301}) 2 | 3 | box.schema.func.create('easy', {language = 'C', if_not_exists = true}) 4 | box.schema.func.create('easy.easy2', {language = 'C', if_not_exists = true}) 5 | 6 | box.schema.user.grant('guest', 'execute', 'function', 'easy', {if_not_exists = true}) 7 | box.schema.user.grant('guest', 'execute', 'function', 'easy.easy2', {if_not_exists = true}) 8 | -------------------------------------------------------------------------------- /examples/easy/src/lib.rs: -------------------------------------------------------------------------------- 1 | use tarantool::proc; 2 | 3 | #[proc] 4 | fn easy() { 5 | println!("hello world"); 6 | } 7 | 8 | #[proc] 9 | fn easy2() { 10 | println!("hello world -- easy2"); 11 | } 12 | -------------------------------------------------------------------------------- /examples/harder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "harder" 3 | version = "0.1.0" 4 | authors = [ 5 | "Dmitriy Koltsov ", 6 | "Anton Melnikov " 7 | ] 8 | edition = "2018" 9 | license = "BSD-2-Clause" 10 | 11 | [dependencies] 12 | serde = "1.0" 13 | 14 | [dependencies.tarantool] 15 | path = "../../tarantool" 16 | 17 | [lib] 18 | test = false 19 | crate-type = ["cdylib"] 20 | -------------------------------------------------------------------------------- /examples/harder/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[tarantool::proc] 2 | fn harder(fields: Vec) { 3 | println!("field_count = {}", fields.len()); 4 | 5 | for val in fields { 6 | println!("val={}", val); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/hardest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hardest" 3 | version = "0.1.0" 4 | authors = [ 5 | "Dmitriy Koltsov ", 6 | "Anton Melnikov " 7 | ] 8 | edition = "2018" 9 | license = "BSD-2-Clause" 10 | 11 | [dependencies] 12 | serde = "1.0" 13 | 14 | [dependencies.tarantool] 15 | path = "../../tarantool" 16 | 17 | [lib] 18 | test = false 19 | crate-type = ["cdylib"] 20 | -------------------------------------------------------------------------------- /examples/hardest/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use tarantool::{ 4 | proc, 5 | space::Space, 6 | tuple::{Encode, Tuple}, 7 | }; 8 | 9 | #[derive(Serialize, Deserialize)] 10 | struct Row { 11 | pub int_field: i32, 12 | pub str_field: String, 13 | } 14 | 15 | impl Encode for Row {} 16 | 17 | #[proc] 18 | fn hardest() -> Tuple { 19 | let space = Space::find("capi_test").unwrap(); 20 | let result = space.insert(&Row { 21 | int_field: 10000, 22 | str_field: "String 2".to_string(), 23 | }); 24 | result.unwrap() 25 | } 26 | -------------------------------------------------------------------------------- /examples/luaopen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "luaopen" 3 | version = "0.1.0" 4 | authors = [ 5 | "Georgy Moshkin ", 6 | ] 7 | edition = "2021" 8 | license = "BSD-2-Clause" 9 | 10 | [dependencies.tarantool] 11 | path = "../../tarantool" 12 | 13 | [lib] 14 | test = false 15 | crate-type = ["cdylib"] 16 | -------------------------------------------------------------------------------- /examples/luaopen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_safety_doc)] 2 | //! This example describes how you can define a simple lua module in rust. 3 | //! 4 | //! To test this module do the following: 5 | //! 6 | //! 0. Compile the example 7 | //! ```text 8 | //! cargo build -p luaopen 9 | //! ``` 10 | //! 11 | //! 1. Setup the LUA_CPATH environment variable, e.g. on linux do 12 | //! ```text 13 | //! export LUA_CPATH=target/debug/lib?.so 14 | //! ``` 15 | //! 16 | //! 2. Start tarantool and run the following commands: 17 | //! ```text 18 | //! $ tarantool 19 | //! Tarantool 2.11.1 20 | //! type 'help' for interactive help 21 | //! 22 | //! tarantool> m = require 'luaopen' 23 | //! you have called `require('luaopen')`, congrats! 24 | //! --- 25 | //! ... 26 | //! 27 | //! tarantool> m.say_hello_to('Bob') 28 | //! --- 29 | //! - Hello, Bob! 30 | //! ... 31 | //! 32 | //! tarantool> m.foo 33 | //! --- 34 | //! - bar 35 | //! ... 36 | //! ``` 37 | 38 | use tarantool::tlua::{self, AsLua}; 39 | 40 | // This function is called, when the lua module is imported via the `require` function. 41 | // 42 | // It is marked `no_mangle` because by default rust modifies function names, 43 | // but lua is looking for exactly the name `luaopen_`. In our case 44 | // the module is called `luaopen`. 45 | // 46 | // Note that there's multiple ways to call native code from tarantool: 47 | // 1. Defining a lua module like this example shows 48 | // 2. Loading the library directly using luajit's ffi module. 49 | // 3. Defining a tarantool stored procedure, e.g. using the `#[proc]` attribute 50 | // (see examples/easy) 51 | // 52 | // Tarantool's stored procedures are implemented via a separate system so if you 53 | // use method 1. and 3. in the same library, than this library will be loaded 54 | // twice. This also means that global data like rust static variables cannot 55 | // be shared between native lua modules (or native ffi functions) and tarantool 56 | // stored procedures. Lua modules with ffi functions don't share this limitation 57 | // however. 58 | #[no_mangle] 59 | #[rustfmt::skip] 60 | pub unsafe extern "C" fn luaopen_luaopen(lua: tlua::LuaState) -> i32 { 61 | println!("you have called `require('luaopen')`, congrats!"); 62 | 63 | // Construct a Lua api object, so we can do stuff with it. 64 | let lua = tlua::Lua::from_static(lua); 65 | 66 | // Push a table onto the lua stack getting a guard value back. 67 | let guard = (&lua).push(tlua::AsTable(( 68 | ("say_hello_to", tlua::Function::new(|name: String| -> String { 69 | format!("Hello, {name}!") 70 | })), 71 | ("foo", "bar"), 72 | ))); 73 | 74 | // Normally the guard pops the values off the stack when drop is called on 75 | // it, but in this case we want the values returned from this function, so 76 | // we call `forget`, which leaves the value on the stack and returns the 77 | // number of values the guard was guarding, which lua interprets as the 78 | // number of return values. 79 | guard.forget() 80 | } 81 | -------------------------------------------------------------------------------- /examples/read/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "read" 3 | version = "0.1.0" 4 | authors = [ 5 | "Dmitriy Koltsov ", 6 | "Anton Melnikov " 7 | ] 8 | edition = "2018" 9 | license = "BSD-2-Clause" 10 | 11 | [dependencies] 12 | serde = "1.0" 13 | 14 | [dependencies.tarantool] 15 | path = "../../tarantool" 16 | 17 | [lib] 18 | test = false 19 | crate-type = ["cdylib"] 20 | -------------------------------------------------------------------------------- /examples/read/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use tarantool::{proc, space::Space, tuple::Encode}; 4 | 5 | #[derive(Serialize, Deserialize, Debug)] 6 | struct Row { 7 | pub int_field: i32, 8 | pub str_field: String, 9 | } 10 | 11 | impl Encode for Row {} 12 | 13 | #[proc] 14 | fn read() { 15 | let space = Space::find("capi_test").unwrap(); 16 | 17 | let key = 10000; 18 | let result = space.get(&(key,)).unwrap(); 19 | assert!(result.is_some()); 20 | 21 | let result = result.unwrap().decode::().unwrap(); 22 | println!("value={:?}", result); 23 | } 24 | -------------------------------------------------------------------------------- /examples/tokio-hyper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-hyper" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "BSD-2-Clause" 6 | 7 | [dependencies] 8 | tarantool = { path = "../../tarantool" } 9 | hyper = { version = "0.14", features = ["full"] } 10 | tokio = { version = "1", features = ["full"] } 11 | futures-util = "*" 12 | http-body-util = "0.1.0-rc.2" 13 | env_logger = "0.9.0" 14 | bytes = "*" 15 | serde = "*" 16 | serde_json = "*" 17 | 18 | [lib] 19 | test = false 20 | crate-type = ["cdylib"] 21 | -------------------------------------------------------------------------------- /examples/tokio-hyper/README.md: -------------------------------------------------------------------------------- 1 | # Example tokio + hyper 2 | 3 | This is a simple example of how you can use **tarantool** with a 4 | **tokio** + **hyper** based web server. 5 | 6 | Simply build the code: 7 | ```bash 8 | cargo build -p tokio-hyper 9 | ``` 10 | 11 | Make sure `LUA_CPATH` environment variable is setup correctly, e.g. if your 12 | target director is `./target`: 13 | ```bash 14 | export LUA_CPATH='./target/debug/lib?.so' 15 | ``` 16 | 17 | Start tarantool with the provided entry script: 18 | ```bash 19 | tarantool examples/tokio-hyper/src/init.lua 20 | ``` 21 | 22 | Now (in another terminal) you can send requests to the web server for example using `curl`: 23 | ```bash 24 | curl '127.0.0.1:3000/add-fruit' -XPOST -d '{"id": 1, "name": "apple", "weight": 3.14 }' 25 | curl '127.0.0.1:3000/add-fruit' -XPOST -d '{"id": 2, "name": "banana", "weight": 2.18 }' 26 | 27 | curl '127.0.0.1:3000/list-fruit' 28 | ``` 29 | 30 | You should see the list of fruit: 31 | ``` 32 | Fruit { id: 1, name: "apple", weight: 3.14 } 33 | Fruit { id: 2, name: "banana", weight: 2.18 } 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/tokio-hyper/src/init.lua: -------------------------------------------------------------------------------- 1 | box.cfg({listen = 3301}) 2 | 3 | box.schema.func.create('tokio_hyper.start_server', {language = 'C', if_not_exists = true}) 4 | box.schema.user.grant('guest', 'execute', 'function', 'tokio_hyper.start_server', {if_not_exists = true}) 5 | 6 | box.schema.space 7 | .create('fruit', { 8 | format = {{'id', 'unsigned'}, {'name', 'string'}, {'weight', 'number'}}, 9 | if_not_exists = true, 10 | }) 11 | :create_index('pk', { if_not_exists = true }) 12 | 13 | box.func['tokio_hyper.start_server']:call() 14 | 15 | require 'console'.start() 16 | -------------------------------------------------------------------------------- /examples/tokio-hyper/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::time::Duration; 3 | 4 | use hyper::service::{make_service_fn, service_fn}; 5 | use hyper::{Body, Method, Request, Response, Server, StatusCode}; 6 | use tokio::sync::mpsc::error::TryRecvError; 7 | use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; 8 | use tokio::sync::Mutex; 9 | 10 | use tarantool::fiber; 11 | use tarantool::index::IteratorType; 12 | use tarantool::space::Space; 13 | use tarantool::tuple; 14 | 15 | #[derive(Debug, Clone, ::serde::Deserialize, ::serde::Serialize)] 16 | struct Fruit { 17 | id: usize, 18 | name: String, 19 | weight: f64, 20 | } 21 | impl tuple::Encode for Fruit {} 22 | 23 | #[derive(Debug)] 24 | enum Cmd { 25 | ListAll, 26 | AddAPieceOfFruit(Fruit), 27 | } 28 | 29 | async fn handle_req( 30 | req: Request, 31 | cmd_tx: UnboundedSender, 32 | fruit_rx: Arc>>>, 33 | ) -> Result, hyper::Error> { 34 | match (req.method(), req.uri().path()) { 35 | // Try doing: `curl '127.0.0.1:3000/list-fruit'` 36 | (&Method::GET, "/list-fruit") => { 37 | cmd_tx.send(Cmd::ListAll).unwrap(); 38 | let fruit = fruit_rx.lock().await.recv().await.unwrap(); 39 | 40 | let mut msg = String::new(); 41 | for single_fruit in fruit { 42 | msg.push_str(&format!("{:?}\n", single_fruit)); 43 | } 44 | 45 | Ok(Response::new(Body::from(msg))) 46 | } 47 | 48 | // Try doing: 49 | // `curl '127.0.0.1:3000/add-fruit' -XPOST -d '{ "id": 1, "name": "apple", "weight": 13.37 }'` 50 | (&Method::POST, "/add-fruit") => { 51 | let body = hyper::body::to_bytes(req.into_body()).await?; 52 | match serde_json::from_slice(body.as_ref()) { 53 | Ok(single_fruit @ Fruit { .. }) => { 54 | cmd_tx.send(Cmd::AddAPieceOfFruit(single_fruit)).unwrap(); 55 | Ok(Response::new(Body::from("ok"))) 56 | } 57 | Err(e) => Ok(Response::new(Body::from(format!("{}", e)))), 58 | } 59 | } 60 | 61 | // Return the 404 Not Found for other routes. 62 | _ => { 63 | let mut not_found = Response::default(); 64 | *not_found.status_mut() = StatusCode::NOT_FOUND; 65 | Ok(not_found) 66 | } 67 | } 68 | } 69 | 70 | #[tarantool::proc] 71 | fn start_server() { 72 | let (cmd_tx, cmd_rx) = unbounded_channel(); 73 | let (fruit_tx, fruit_rx) = unbounded_channel::>(); 74 | let fruit_rx = Arc::new(Mutex::new(fruit_rx)); 75 | 76 | let jh = std::thread::spawn(move || { 77 | tokio::runtime::Builder::new_multi_thread() 78 | .enable_all() 79 | .build() 80 | .expect("Failed building the Runtime") 81 | .block_on(async move { 82 | let service = make_service_fn(move |_| { 83 | let cmd_tx = cmd_tx.clone(); 84 | let fruit_rx = fruit_rx.clone(); 85 | async move { 86 | Ok::<_, hyper::Error>(service_fn(move |req| { 87 | handle_req(req, cmd_tx.clone(), fruit_rx.clone()) 88 | })) 89 | } 90 | }); 91 | 92 | let addr = ([127, 0, 0, 1], 3000).into(); 93 | let server = Server::bind(&addr).serve(service); 94 | 95 | println!("Listening on http://{}", addr); 96 | 97 | server.await?; 98 | Ok::<(), Box>(()) 99 | }) 100 | }); 101 | 102 | // Detach the thread 103 | drop(jh); 104 | 105 | static mut FIBER_JOIN_HANDLE: Option> = None; 106 | let jh = fiber::start(move || { 107 | let mut cmd_rx = cmd_rx; 108 | let space_fruit = Space::find("fruit").unwrap(); 109 | loop { 110 | match cmd_rx.try_recv() { 111 | Err(TryRecvError::Empty) => { 112 | fiber::sleep(Duration::from_millis(100)); 113 | continue; 114 | } 115 | Err(TryRecvError::Disconnected) => break, 116 | Ok(Cmd::ListAll) => { 117 | let fruit = space_fruit 118 | .select(IteratorType::All, &()) 119 | .unwrap() 120 | .map(|t| t.decode::().unwrap()) 121 | .collect(); 122 | fruit_tx.send(fruit).unwrap(); 123 | } 124 | Ok(Cmd::AddAPieceOfFruit(single_fruit)) => { 125 | space_fruit.replace(&single_fruit).unwrap(); 126 | } 127 | } 128 | } 129 | }); 130 | // There's currently no way of detaching a fiber without leaking memory, 131 | // so we have to store it's join handle somewhere. 132 | unsafe { 133 | FIBER_JOIN_HANDLE = Some(jh); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /examples/write/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "write" 3 | version = "0.1.0" 4 | authors = [ 5 | "Dmitriy Koltsov ", 6 | "Anton Melnikov " 7 | ] 8 | edition = "2018" 9 | license = "BSD-2-Clause" 10 | 11 | [dependencies.tarantool] 12 | path = "../../tarantool" 13 | 14 | [lib] 15 | test = false 16 | crate-type = ["cdylib"] 17 | -------------------------------------------------------------------------------- /examples/write/src/lib.rs: -------------------------------------------------------------------------------- 1 | use tarantool::{error::Error, fiber::sleep, proc, space::Space, transaction::transaction}; 2 | 3 | #[proc] 4 | fn write() -> Result<(i32, String), String> { 5 | let space = Space::find("capi_test").ok_or_else(|| "Can't find space capi_test".to_string())?; 6 | 7 | let row = (1, "22".to_string()); 8 | 9 | transaction(|| -> Result<(), Error> { 10 | space.replace(&row)?; 11 | Ok(()) 12 | }) 13 | .unwrap(); 14 | 15 | sleep(std::time::Duration::from_millis(1)); 16 | Ok(row) 17 | } 18 | -------------------------------------------------------------------------------- /perf-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "perf-test" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tarantool = { path = "../tarantool", features = [ "internal_test" ] } 10 | serde = { version = "*", features = ["derive"] } 11 | rmp-serde = "1" 12 | 13 | [lib] 14 | test = false 15 | crate-type = ["cdylib"] 16 | -------------------------------------------------------------------------------- /perf-test/run.lua: -------------------------------------------------------------------------------- 1 | local fio = require('fio') 2 | local json = require('json') 3 | local log = require('log') 4 | 5 | local tmpdir = fio.tempdir() 6 | 7 | box.cfg{ 8 | log_level = 'verbose', 9 | listen = '127.0.0.1:0', 10 | wal_mode = 'none', 11 | memtx_dir = tmpdir, 12 | } 13 | 14 | fio.rmtree(tmpdir) 15 | 16 | -- Init test database 17 | box.once('bootstrap_tests', function() 18 | box.schema.user.grant('guest', 'read,write,execute,create,drop', 'universe') 19 | 20 | box.schema.func.create('test_stored_proc') 21 | end) 22 | 23 | function test_stored_proc(a, b) 24 | return a + b 25 | end 26 | 27 | function target_dir() 28 | if rawget(_G, '_target_dir') == nil then 29 | local data = io.popen('cargo metadata --format-version 1'):read('*l') 30 | rawset(_G, '_target_dir', json.decode(data).target_directory) 31 | end 32 | return _target_dir 33 | end 34 | 35 | function build_mode() 36 | local build_mode_env = os.getenv('TARANTOOL_MODULE_BUILD_MODE') 37 | if not build_mode_env then 38 | build_mode_env = 'debug' 39 | end 40 | return build_mode_env 41 | end 42 | 43 | -- Add test runner library location to lua search path 44 | package.cpath = string.format( 45 | '%s/%s/?.so;%s/%s/?.dylib;%s', 46 | target_dir(), build_mode(), 47 | target_dir(), build_mode(), 48 | package.cpath 49 | ) 50 | 51 | box.schema.func.create('libperf_test.bench_netbox', {language = 'C'}) 52 | box.schema.func.create('libperf_test.bench_network_client', {language = 'C'}) 53 | box.schema.func.create('libperf_test.bench_custom_encode', {language = 'C'}) 54 | box.schema.func.create('libperf_test.bench_custom_decode', {language = 'C'}) 55 | box.schema.func.create('libperf_test.bench_serde_encode', {language = 'C'}) 56 | box.schema.func.create('libperf_test.bench_serde_decode', {language = 'C'}) 57 | box.schema.func.create('libperf_test.l_print_stats', {language = 'C'}) 58 | box.schema.func.create('libperf_test.l_n_iters', {language = 'C'}) 59 | 60 | function bench_lua_netbox() 61 | local clock = require('clock') 62 | local net_box = require("net.box") 63 | 64 | local connect_deadline = clock.monotonic() + 3 -- seconds 65 | local conn 66 | repeat 67 | conn = net_box:connect(box.info.listen) 68 | local ok = conn:wait_connected(clock.monotonic() - connect_deadline) 69 | if clock.monotonic() > connect_deadline then 70 | error(string.format('Failed to establish a connection to port %s', box.info.listen)) 71 | end 72 | until ok or clock.monotonic() > connect_deadline 73 | 74 | local samples = {} 75 | local n = box.func['libperf_test.l_n_iters']:call() 76 | 77 | -- benchmarking loop 78 | for i = 1, n do 79 | local start = clock.monotonic64() 80 | local res = conn:call('test_stored_proc', {1, 2}) 81 | samples[i] = clock.monotonic64() - start 82 | end 83 | 84 | conn:close() 85 | box.func['libperf_test.l_print_stats']:call{"lua_netbox", samples} 86 | end 87 | 88 | print("================ iproto_clients =================") 89 | bench_lua_netbox() 90 | box.func['libperf_test.bench_netbox']:call() 91 | box.func['libperf_test.bench_network_client']:call() 92 | print() 93 | print("============= msgpack_serialization =============") 94 | box.func['libperf_test.bench_custom_encode']:call() 95 | box.func['libperf_test.bench_serde_encode']:call() 96 | box.func['libperf_test.bench_custom_decode']:call() 97 | box.func['libperf_test.bench_serde_decode']:call() 98 | os.exit(0) 99 | -------------------------------------------------------------------------------- /perf-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, time::Duration}; 2 | 3 | use tarantool::{fiber, proc, time::Instant}; 4 | 5 | const N_ITERS: usize = 10_000; 6 | const PREHEAT_ITERS: usize = 1_000; 7 | 8 | #[proc] 9 | fn l_n_iters() -> usize { 10 | N_ITERS 11 | } 12 | 13 | mod iproto_clients { 14 | use super::{harness_iter, harness_iter_async, print_stats}; 15 | use tarantool::test::util::listen_port; 16 | use tarantool::{ 17 | fiber, 18 | net_box::{Conn, ConnOptions, Options}, 19 | network::client::{AsClient as _, Client}, 20 | proc, 21 | }; 22 | 23 | #[proc] 24 | fn bench_network_client() { 25 | let client = fiber::block_on(Client::connect("localhost", listen_port())).unwrap(); 26 | let samples = harness_iter_async(|| async { 27 | client.call("test_stored_proc", &(1, 2)).await.unwrap(); 28 | }); 29 | print_stats("network_client", samples); 30 | } 31 | 32 | #[proc] 33 | fn bench_netbox() { 34 | let conn = Conn::new( 35 | ("localhost", listen_port()), 36 | ConnOptions { 37 | ..ConnOptions::default() 38 | }, 39 | None, 40 | ) 41 | .unwrap(); 42 | conn.wait_connected(None).unwrap(); 43 | let samples = harness_iter(|| { 44 | conn.call("test_stored_proc", &(1, 2), &Options::default()) 45 | .unwrap(); 46 | }); 47 | print_stats("netbox", samples); 48 | } 49 | } 50 | 51 | mod msgpack_serialization { 52 | use super::{harness_iter, print_stats}; 53 | use serde::{Deserialize, Serialize}; 54 | use tarantool::msgpack::*; 55 | use tarantool::proc; 56 | 57 | const HEIGHT: usize = 5; 58 | const DEGREE: usize = 4; 59 | 60 | #[derive(Encode, Decode, Serialize, Deserialize)] 61 | enum Foo { 62 | Bar(usize), 63 | Baz(usize), 64 | None, 65 | } 66 | 67 | #[derive(Encode, Decode, Serialize, Deserialize)] 68 | struct Node { 69 | s: String, 70 | n: usize, 71 | e: Foo, 72 | leaves: Vec, 73 | } 74 | 75 | fn gen_tree(height: usize, degree: usize) -> Node { 76 | let mut node = Node { 77 | leaves: vec![], 78 | s: format!("height is {}", height), 79 | n: height * degree, 80 | e: Foo::Bar(height), 81 | }; 82 | if height == 0 { 83 | return node; 84 | } 85 | for _ in 0..degree { 86 | // Recursion should be ok for testing purposes 87 | node.leaves.push(gen_tree(height - 1, degree)); 88 | } 89 | node 90 | } 91 | 92 | #[proc] 93 | fn bench_custom_encode() { 94 | let tree = gen_tree(HEIGHT, DEGREE); 95 | let samples = harness_iter(|| { 96 | let _bytes = encode(&tree); 97 | }); 98 | print_stats("custom_encode", samples); 99 | } 100 | 101 | #[proc] 102 | fn bench_custom_decode() { 103 | let tree = gen_tree(HEIGHT, DEGREE); 104 | let bytes = encode(&tree); 105 | let samples = harness_iter(|| { 106 | let _got_tree: Node = decode(&bytes).unwrap(); 107 | }); 108 | print_stats("custom_decode", samples); 109 | } 110 | 111 | #[proc] 112 | fn bench_serde_encode() { 113 | let tree = gen_tree(HEIGHT, DEGREE); 114 | let samples = harness_iter(|| { 115 | let _bytes = rmp_serde::to_vec(&tree).unwrap(); 116 | }); 117 | print_stats("serde_encode", samples); 118 | } 119 | 120 | #[proc] 121 | fn bench_serde_decode() { 122 | let tree = gen_tree(HEIGHT, DEGREE); 123 | let bytes = rmp_serde::to_vec(&tree).unwrap(); 124 | let samples = harness_iter(|| { 125 | let _got_tree: Node = rmp_serde::from_slice(&bytes).unwrap(); 126 | }); 127 | print_stats("serde_decode", samples); 128 | } 129 | } 130 | 131 | #[proc] 132 | fn l_print_stats(fn_name: &str, samples: Vec) { 133 | assert_eq!(samples.len(), N_ITERS); 134 | print_stats(fn_name, samples.iter().map(|v| *v as u128).collect()) 135 | } 136 | 137 | #[allow(clippy::unit_arg)] 138 | fn harness_iter(mut f: impl FnMut()) -> Vec { 139 | // Preheating 140 | for _ in 0..PREHEAT_ITERS { 141 | std::hint::black_box(f()); 142 | } 143 | 144 | let mut samples = Vec::with_capacity(N_ITERS); 145 | for _ in 0..N_ITERS { 146 | let start = Instant::now_accurate(); 147 | std::hint::black_box(f()); 148 | samples.push(start.elapsed().as_nanos()); 149 | } 150 | samples 151 | } 152 | 153 | #[allow(clippy::unit_arg)] 154 | fn harness_iter_async(mut f: impl FnMut() -> F) -> Vec { 155 | // Preheating 156 | fiber::block_on(async { 157 | for _ in 0..PREHEAT_ITERS { 158 | std::hint::black_box(f().await); 159 | } 160 | }); 161 | 162 | let mut samples = Vec::with_capacity(N_ITERS); 163 | fiber::block_on(async { 164 | for _ in 0..N_ITERS { 165 | let start = Instant::now_accurate(); 166 | std::hint::black_box(f().await); 167 | samples.push(start.elapsed().as_nanos()); 168 | } 169 | }); 170 | samples 171 | } 172 | 173 | fn print_stats(fn_name: &str, samples: Vec) { 174 | let mean: f64 = samples.iter().sum::() as f64 / N_ITERS as f64; 175 | let std_dev: f64 = (samples 176 | .into_iter() 177 | .fold(0f64, |sum, sample| sum + (mean - sample as f64).powi(2)) 178 | / N_ITERS as f64) 179 | .sqrt(); 180 | println!( 181 | "{}: {:?} +- {:?}", 182 | fn_name, 183 | Duration::from_nanos(mean as u64), 184 | Duration::from_nanos(std_dev as u64) 185 | ) 186 | } 187 | -------------------------------------------------------------------------------- /tarantool-proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Georgy Moshkin ", 4 | ] 5 | name = "tarantool-proc" 6 | description = "Tarantool proc macros" 7 | version = "4.0.1" 8 | edition = "2021" 9 | license = "BSD-2-Clause" 10 | documentation = "https://docs.rs/tarantool-proc/" 11 | repository = "https://github.com/picodata/tarantool-module" 12 | rust-version = "1.82" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | syn = { version = "^1.0", features = [ "full", "extra-traits" ] } 19 | quote = "^1.0" 20 | proc-macro2 = "^1.0" 21 | darling = "0.14.2" 22 | proc-macro-error2 = "2" 23 | 24 | [features] 25 | stored_procs_slice = [] 26 | -------------------------------------------------------------------------------- /tarantool-proc/src/test.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | 3 | use crate::default_tarantool_crate_path; 4 | 5 | macro_rules! unwrap_or_compile_error { 6 | ($expr:expr) => { 7 | match $expr { 8 | Ok(v) => v, 9 | Err(e) => { 10 | return e.to_compile_error().into(); 11 | } 12 | } 13 | }; 14 | } 15 | 16 | pub fn impl_macro_attribute( 17 | attr: proc_macro::TokenStream, 18 | item: proc_macro::TokenStream, 19 | ) -> proc_macro::TokenStream { 20 | let fn_item = syn::parse_macro_input!(item as syn::ItemFn); 21 | let ctx = unwrap_or_compile_error!(Context::from_args(attr.into())); 22 | let fn_name = &fn_item.sig.ident; 23 | let test_name = fn_name.to_string(); 24 | let unique_name = format!("TARANTOOL_MODULE_TEST_CASE_{}", test_name.to_uppercase()); 25 | let test_name_ident = syn::Ident::new(&unique_name, fn_name.span()); 26 | let Context { 27 | tarantool, 28 | section, 29 | linkme, 30 | should_panic, 31 | } = ctx; 32 | 33 | let fn_item = if fn_item.sig.asyncness.is_some() { 34 | let body = fn_item.block; 35 | quote! { 36 | fn #fn_name() { 37 | #tarantool::fiber::block_on(async { #body }) 38 | } 39 | } 40 | } else { 41 | quote! { 42 | #fn_item 43 | } 44 | }; 45 | 46 | quote! { 47 | #[#linkme::distributed_slice(#section)] 48 | #[linkme(crate = #linkme)] 49 | #[used] 50 | static #test_name_ident: #tarantool::test::TestCase = #tarantool::test::TestCase::new( 51 | ::std::concat!(::std::module_path!(), "::", #test_name), 52 | #fn_name, 53 | #should_panic, 54 | ); 55 | 56 | #fn_item 57 | } 58 | .into() 59 | } 60 | 61 | #[derive(Debug)] 62 | struct Context { 63 | tarantool: syn::Path, 64 | section: syn::Path, 65 | linkme: syn::Path, 66 | should_panic: syn::Expr, 67 | } 68 | 69 | impl Context { 70 | fn from_args(tokens: proc_macro2::TokenStream) -> Result { 71 | let mut tarantool = default_tarantool_crate_path(); 72 | let mut linkme = None; 73 | let mut section = None; 74 | let mut should_panic = syn::parse_quote! { false }; 75 | 76 | syn::parse::Parser::parse2( 77 | |input: syn::parse::ParseStream| -> Result<(), syn::Error> { 78 | while !input.is_empty() { 79 | let ident: syn::Ident = input.parse()?; 80 | if ident == "tarantool" { 81 | input.parse::()?; 82 | let value: syn::LitStr = input.parse()?; 83 | tarantool = value.parse()?; 84 | } else if ident == "linkme" { 85 | input.parse::()?; 86 | let value: syn::LitStr = input.parse()?; 87 | linkme = Some(value.parse()?); 88 | } else if ident == "section" { 89 | input.parse::()?; 90 | let value: syn::LitStr = input.parse()?; 91 | section = Some(value.parse()?); 92 | } else if ident == "should_panic" { 93 | if input.parse::().is_ok() { 94 | should_panic = input.parse()?; 95 | } else { 96 | should_panic = syn::parse_quote! { true }; 97 | } 98 | } else { 99 | return Err(syn::Error::new( 100 | ident.span(), 101 | format!("unknown argument `{ident}`, expected one of `tarantool`, `linkme`, `section`, `should_panic`"), 102 | )); 103 | } 104 | 105 | if !input.is_empty() { 106 | input.parse::()?; 107 | } 108 | } 109 | 110 | Ok(()) 111 | }, 112 | tokens, 113 | )?; 114 | 115 | let section = section 116 | .unwrap_or_else(|| syn::parse_quote! { #tarantool::test::TARANTOOL_MODULE_TESTS }); 117 | 118 | let linkme = linkme.unwrap_or_else(|| syn::parse_quote! { #tarantool::linkme }); 119 | 120 | Ok(Self { 121 | tarantool, 122 | section, 123 | linkme, 124 | should_panic, 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tarantool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tarantool" 3 | description = "Tarantool rust bindings" 4 | version = "10.1.0" 5 | authors = [ 6 | "Dmitriy Koltsov ", 7 | "Georgy Moshkin ", 8 | "Anton Melnikov ", 9 | "Egor Ivkov ", 10 | ] 11 | edition = "2018" 12 | license = "BSD-2-Clause" 13 | readme = "../README.md" 14 | documentation = "https://docs.rs/tarantool/" 15 | repository = "https://github.com/picodata/tarantool-module" 16 | keywords = ["ffi", "database", "tarantool"] 17 | categories = ["database"] 18 | rust-version = "1.82" 19 | 20 | [dependencies] 21 | base64 = "0.13" 22 | bitflags = "1.2" 23 | dec = { version = "0.4.8", optional = true } 24 | libloading = "0.8" 25 | thiserror = "1.0.30" 26 | libc = { version = "0.2", features = ["extra_traits"] } 27 | log = "0.4" 28 | once_cell = "1.4.0" 29 | tlua = { path = "../tlua", version = "7" } 30 | refpool = { version = "0.4.3", optional = true } 31 | rmp = "0.8.11" 32 | rmp-serde = "1.1" 33 | rmpv = { version = "1.0.0", features = ["with-serde"] } 34 | serde = { version = "1.0", features = ["derive"] } 35 | serde_json = "1.0" 36 | serde_bytes = "^0" 37 | sha-1 = "0.9" 38 | md-5 = "0.10" 39 | tarantool-proc = { path = "../tarantool-proc", version = "4" } 40 | uuid = "0.8.2" 41 | futures = "0.3.25" 42 | linkme = "0.3" 43 | async-trait = "0.1.64" 44 | tester = { version = "0.7.0", optional = true } 45 | time = "0.3.37" 46 | crossbeam-queue = { version = "0.3.8", optional = true } 47 | pretty_assertions = { version = "1.4", optional = true } 48 | tempfile = { version = "3.9", optional = true } 49 | va_list = ">=0.1.4" 50 | tokio = { version = "1", features = ["sync", "rt", "time"], optional = true } 51 | anyhow = { version = "1", optional = true } 52 | openssl = { version = "0.10", optional = true } 53 | 54 | [features] 55 | default = ["net_box", "network_client"] 56 | net_box = ["refpool"] 57 | picodata = ["crossbeam-queue"] 58 | tokio_components = ["picodata", "tokio"] 59 | network_client = ["openssl"] 60 | test = ["tester"] 61 | all = ["default", "test"] 62 | internal_test = ["test", "tlua/internal_test", "pretty_assertions", "tempfile"] 63 | # This feature switches tarantool module decimal support to use rust dec crate 64 | # instead of decimal impl available in tarantool. 65 | # This feature has two use cases,the primary one is decimal support for rust module used with 66 | # vanilla tarantool (without our fork). In vanilla many needed symbols are not exported, 67 | # so we cant use tarantool builtin decimal. 68 | # Another case that is considered as a temporary hack are sbroad unit tests. Currently they 69 | # are run outside tarantool. So tarantool symbols are not available there. Thus standalone 70 | # option usage. This is expected to be resolved. 71 | # Beware: having two implementations of decimal (one in tarantool and one on the rust side) 72 | # in use at the same time is problematic because libdecnumber versions used by both of the 73 | # options are not exactly the same. Thus deviations in behaviour between them are possible 74 | standalone_decimal = ["dec"] 75 | stored_procs_slice = ["tarantool-proc/stored_procs_slice"] 76 | 77 | [dev-dependencies] 78 | time-macros = "0.2.6" 79 | pretty_assertions = "1.4" 80 | -------------------------------------------------------------------------------- /tarantool/src/access_control.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "picodata")] 2 | 3 | use std::ffi::CString; 4 | 5 | use crate::error; 6 | use crate::ffi::tarantool as ffi; 7 | use crate::space::SpaceId; 8 | 9 | /// This is a direct translation of `box_privilege_type` enum from `user_def.h` 10 | #[repr(u16)] 11 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 12 | pub enum PrivType { 13 | /// SELECT 14 | Read = 1, 15 | /// INSERT, UPDATE, UPSERT, DELETE, REPLACE 16 | Write = 2, 17 | /// CALL 18 | Execute = 4, 19 | /// SESSION 20 | Session = 8, 21 | /// USAGE 22 | Usage = 16, 23 | /// CREATE 24 | Create = 32, 25 | /// DROP 26 | Drop = 64, 27 | /// ALTER 28 | Alter = 128, 29 | /// REFERENCE - required by ANSI - not implemented 30 | Reference = 256, 31 | /// TRIGGER - required by ANSI - not implemented 32 | Trigger = 512, 33 | /// INSERT - required by ANSI - not implemented 34 | Insert = 1024, 35 | /// UPDATE - required by ANSI - not implemented 36 | Update = 2048, 37 | /// DELETE - required by ANSI - not implemented 38 | Delete = 4096, 39 | /// This is never granted, but used internally. 40 | Grant = 8192, 41 | /// Never granted, but used internally. 42 | Revoke = 16384, 43 | All = u16::MAX, 44 | } 45 | 46 | /// This function is a wrapper around similarly named one in tarantool. 47 | /// It allows to run access check for the current user against 48 | /// specified space and access type. Most relevant access types are read and write. 49 | pub fn box_access_check_space(space_id: SpaceId, user_access: PrivType) -> crate::Result<()> { 50 | let ret = unsafe { ffi::box_access_check_space(space_id, user_access as u16) }; 51 | if ret == -1 { 52 | Err(error::Error::Tarantool(error::TarantoolError::last())) 53 | } else { 54 | Ok(()) 55 | } 56 | } 57 | 58 | /// This is a direct translation of `box_schema_object_type` enum from `schema_def.h` 59 | #[repr(u32)] 60 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)] 61 | pub enum SchemaObjectType { 62 | #[default] 63 | Unknown = 0, 64 | Universe = 1, 65 | Space = 2, 66 | Function = 3, 67 | User = 4, 68 | Role = 5, 69 | Sequence = 6, 70 | Collation = 7, 71 | ObjectTypeMax = 8, 72 | 73 | EntitySpace = 9, 74 | EntityFunction = 10, 75 | EntityUser = 11, 76 | EntityRole = 12, 77 | EntitySequence = 13, 78 | EntityCollation = 14, 79 | EntityTypeMax = 15, 80 | } 81 | 82 | impl SchemaObjectType { 83 | fn is_entity(&self) -> bool { 84 | *self as u32 > SchemaObjectType::ObjectTypeMax as u32 85 | } 86 | } 87 | 88 | /// This function allows to perform various permission checks externally. 89 | /// Note that there are no checks performed for harmless combinations 90 | /// it doesnt make sense, i e execute space. This shouldnt lead to any 91 | /// critical issues like UB but is just pointless from the application perspective. 92 | /// 93 | /// # Panicking 94 | /// 95 | /// Note that not all combinations of parameters are valid. 96 | /// 97 | /// For example Entity* object types can only be used with [`PrivType::Grant`] 98 | /// or [`PrivType::Revoke`]. 99 | /// Otherwise because of how this is structured inside tarantool such a call 100 | /// leads to undefined behavior. 101 | /// 102 | /// Another such example is that when using Grant or Revoke owner id must be set 103 | /// to current user because in this context the owner is the user who grants 104 | /// the permission (grantor). This works because for Grant or Revoke 105 | /// box_access_check_ddl is not enough. For proper permission check you need 106 | /// to additionally perform checks contained in priv_def_check C function. 107 | /// 108 | /// So given these limitations box_access_check_ddl guards against 109 | /// invalid combinations that lead to UB by panicking instead. 110 | pub fn box_access_check_ddl( 111 | object_name: &str, 112 | object_id: u32, 113 | owner_id: u32, 114 | object_type: SchemaObjectType, 115 | access: PrivType, 116 | ) -> crate::Result<()> { 117 | assert!( 118 | !object_type.is_entity() || matches!(access, PrivType::Grant | PrivType::Revoke), 119 | "Entity scoped permissons can be checked only with Grant or Revoke" 120 | ); 121 | 122 | if matches!(access, PrivType::Grant | PrivType::Revoke) { 123 | assert_eq!( 124 | owner_id, 125 | crate::session::uid().expect("there must be current user"), 126 | "This is incorrect use of the API. For grant and revoke owner_id must be current user (grantor)." 127 | ) 128 | } 129 | 130 | let name = CString::new(object_name).expect("object name may not contain interior null bytes"); 131 | let ret = unsafe { 132 | ffi::box_access_check_ddl( 133 | name.as_ptr(), 134 | object_id, 135 | owner_id, 136 | object_type as u32, 137 | access as u16, 138 | ) 139 | }; 140 | if ret == -1 { 141 | Err(error::Error::Tarantool(error::TarantoolError::last())) 142 | } else { 143 | Ok(()) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tarantool/src/auth.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[cfg(not(feature = "picodata"))] 4 | crate::define_str_enum! { 5 | #[derive(Default)] 6 | pub enum AuthMethod { 7 | #[default] 8 | ChapSha1 = "chap-sha1", 9 | } 10 | } 11 | 12 | #[cfg(not(feature = "picodata"))] 13 | impl AuthMethod { 14 | pub const DEFAULT: Self = Self::ChapSha1; 15 | } 16 | 17 | #[cfg(feature = "picodata")] 18 | crate::define_str_enum! { 19 | #[derive(Default)] 20 | pub enum AuthMethod { 21 | #[default] 22 | Md5 = "md5", 23 | 24 | ScramSha256 = "scram-sha256", 25 | ChapSha1 = "chap-sha1", 26 | Ldap = "ldap", 27 | } 28 | } 29 | 30 | #[cfg(feature = "picodata")] 31 | impl AuthMethod { 32 | pub const DEFAULT: Self = Self::Md5; 33 | } 34 | 35 | #[cfg(feature = "picodata")] 36 | mod picodata { 37 | use super::AuthMethod; 38 | use crate::ffi::tarantool as ffi; 39 | use std::mem::MaybeUninit; 40 | use std::ops::Range; 41 | 42 | pub(super) fn auth_data_prepare(method: &AuthMethod, user: &str, password: &str) -> String { 43 | let Range { 44 | start: pwd_start, 45 | end: pwd_end, 46 | } = password.as_bytes().as_ptr_range(); 47 | let Range { 48 | start: user_start, 49 | end: user_end, 50 | } = user.as_bytes().as_ptr_range(); 51 | let Range { 52 | start: auth_start, 53 | end: auth_end, 54 | } = method.as_str().as_bytes().as_ptr_range(); 55 | let mut data = MaybeUninit::uninit(); 56 | let mut data_end = MaybeUninit::uninit(); 57 | let svp = unsafe { ffi::box_region_used() }; 58 | unsafe { 59 | ffi::box_auth_data_prepare( 60 | auth_start as _, 61 | auth_end as _, 62 | pwd_start as _, 63 | pwd_end as _, 64 | user_start as _, 65 | user_end as _, 66 | data.as_mut_ptr(), 67 | data_end.as_mut_ptr(), 68 | ); 69 | } 70 | let data = unsafe { data.assume_init() }; 71 | let data_end = unsafe { data_end.assume_init() }; 72 | let bytes: &[u8] = unsafe { 73 | std::slice::from_raw_parts(data as *const u8, data_end.offset_from(data) as usize) 74 | }; 75 | let auth_data = rmp_serde::from_slice(bytes).unwrap(); 76 | unsafe { ffi::box_region_truncate(svp) }; 77 | auth_data 78 | } 79 | } 80 | 81 | pub struct AuthData(String); 82 | 83 | impl AuthData { 84 | #[cfg(feature = "picodata")] 85 | pub fn new(method: &AuthMethod, user: &str, password: &str) -> Self { 86 | let data = picodata::auth_data_prepare(method, user, password); 87 | Self(data) 88 | } 89 | 90 | pub fn into_string(self) -> String { 91 | self.0 92 | } 93 | } 94 | 95 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] 96 | pub struct AuthDef { 97 | pub method: AuthMethod, 98 | /// Base64 encoded digest. 99 | pub data: String, 100 | } 101 | 102 | impl AuthDef { 103 | pub fn new(method: AuthMethod, data: String) -> Self { 104 | Self { method, data } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tarantool/src/cbus/sync/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(any(feature = "picodata", doc))] 2 | 3 | //! A synchronous channels for popular runtimes. 4 | //! Synchronous channel - means that channel has internal buffer with user-defined capacity. 5 | //! Synchronous channel differs against of unbounded channel in the semantics of the sender: if 6 | //! channel buffer is full then all sends called from producer will block a runtime, until channel 7 | //! buffer is freed. 8 | //! 9 | //! It is important to use a channel that suits the runtime in which the producer works. 10 | 11 | /// A channels for messaging between an OS thread (producer) and tarantool cord (consumer). 12 | pub mod std; 13 | /// A channels for messaging between a tokio task (producer) and tarantool cord (consumer). 14 | pub mod tokio; 15 | -------------------------------------------------------------------------------- /tarantool/src/clock.rs: -------------------------------------------------------------------------------- 1 | //! The `clock` module returns time values derived from the Posix / C 2 | //! [CLOCK_GETTIME](http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html) 3 | //! function or equivalent. 4 | //! 5 | //! Most functions in the module return a number of seconds; functions with names followed by “64” 6 | //! return a 64-bit number of nanoseconds. 7 | //! 8 | //! - [time()](fn.time.html) - Get the wall clock time in seconds 9 | //! - [time64()](fn.time64.html) - Get the wall clock time in nanoseconds 10 | //! - [monotonic()](fn.monotonic.html) - Get the monotonic time in seconds 11 | //! - [monotonic64()](fn.monotonic64.html) - Get the monotonic time in nanoseconds 12 | //! - [proc()](fn.proc.html) - Get the processor time in seconds 13 | //! - [proc64()](fn.proc64.html) - Get the processor time in nanoseconds 14 | //! - [thread()](fn.thread.html) - Get the thread time in seconds 15 | //! - [thread64()](fn.thread64.html) - Get the thread time in nanoseconds 16 | //! 17 | //! See also: 18 | //! - [Lua reference: Module clock](https://www.tarantool.io/en/doc/latest/reference/reference_lua/clock/) 19 | //! - [C API reference: Module clock](https://www.tarantool.io/en/doc/latest/dev_guide/reference_capi/clock/) 20 | 21 | use std::time::Duration; 22 | 23 | /// A large duration, effectively infinite for all practical purposes. 24 | /// 25 | /// This value can be used as a default timeout value whenever there's no 26 | /// obvious limit but the API requires an explicit value. 27 | /// 28 | /// Is equivalent to 100 years. 29 | pub const INFINITY: Duration = Duration::from_secs(100 * 365 * 24 * 60 * 60); 30 | 31 | use crate::ffi::tarantool as ffi; 32 | 33 | /// The wall clock time in seconds. 34 | /// 35 | /// Derived from C function `clock_gettime(CLOCK_REALTIME)`. 36 | /// This is the best function for knowing what the official time is, as determined by the system administrator. 37 | /// 38 | /// Return: seconds since epoch (1970-01-01 00:00:00), adjusted. 39 | /// 40 | /// Example: 41 | /// ```no_run 42 | /// // This will print an approximate number of years since 1970. 43 | /// use tarantool::clock::time; 44 | /// println!("{}", time() / (365. * 24. * 60. * 60.)); 45 | /// ``` 46 | /// 47 | /// See also: [fiber::time()](../fiber/fn.time.html), [fiber::time64()](../fiber/fn.time64.html) 48 | #[inline(always)] 49 | pub fn time() -> f64 { 50 | unsafe { ffi::clock_realtime() } 51 | } 52 | 53 | /// The wall clock time in nanoseconds since epoch. 54 | /// 55 | /// Example: 56 | /// ```no_run 57 | /// // This will print an approximate number of years since 1970. 58 | /// use tarantool::clock::time64; 59 | /// println!("{}", time64() / (365 * 24 * 60 * 60)); 60 | /// ``` 61 | /// See: [time()](fn.time.html) 62 | #[inline(always)] 63 | pub fn time64() -> u64 { 64 | unsafe { ffi::clock_realtime64() } 65 | } 66 | 67 | /// The monotonic time. 68 | /// 69 | /// Derived from C function `clock_gettime(CLOCK_MONOTONIC)`. 70 | /// Monotonic time is similar to wall clock time but is not affected by changes to or from daylight saving time, or by 71 | /// changes done by a user. This is the best function to use with benchmarks that need to calculate elapsed time. 72 | /// 73 | /// Return: seconds or nanoseconds since the last time that the computer was booted. 74 | /// Return type: `u64` or `f64` 75 | /// 76 | /// Example: 77 | /// ```no_run 78 | /// // This will print nanoseconds since the start. 79 | /// use tarantool::clock::monotonic64; 80 | /// println!("{}", monotonic64()); 81 | /// ``` 82 | #[inline(always)] 83 | pub fn monotonic() -> f64 { 84 | unsafe { ffi::clock_monotonic() } 85 | } 86 | 87 | /// See: [monotonic()](fn.monotonic.html) 88 | #[inline(always)] 89 | pub fn monotonic64() -> u64 { 90 | unsafe { ffi::clock_monotonic64() } 91 | } 92 | 93 | /// The processor time. 94 | /// 95 | /// Derived from C function `clock_gettime(CLOCK_PROCESS_CPUTIME_ID)`. 96 | /// This is the best function to use with benchmarks that need to calculate the amount of time for which CPU was used. 97 | /// 98 | /// Return: seconds or nanoseconds since processor start. 99 | /// Return type: `u64` or `f64` 100 | /// 101 | /// Example: 102 | /// ```no_run 103 | /// // This will print nanoseconds in the CPU since the start. 104 | /// use tarantool::clock::process64; 105 | /// println!("{}", process64()); 106 | /// ``` 107 | #[inline(always)] 108 | pub fn process() -> f64 { 109 | unsafe { ffi::clock_process() } 110 | } 111 | 112 | /// See: [process()](fn.process.html) 113 | #[inline(always)] 114 | pub fn process64() -> u64 { 115 | unsafe { ffi::clock_process64() } 116 | } 117 | 118 | /// The thread time. 119 | /// 120 | /// Derived from C function `clock_gettime(CLOCK_THREAD_CPUTIME_ID)`. 121 | /// This is the best function to use with benchmarks that need to calculate hthe amount of time for which a CPU thread was used. 122 | /// 123 | /// Return: seconds or nanoseconds since the transaction processor thread started. 124 | /// Return type: `u64` or `f64` 125 | /// 126 | /// Example: 127 | /// ```no_run 128 | /// // This will print seconds in the thread since the start. 129 | /// use tarantool::clock::thread64; 130 | /// println!("{}", thread64()); 131 | /// ``` 132 | #[inline(always)] 133 | pub fn thread() -> f64 { 134 | unsafe { ffi::clock_thread() } 135 | } 136 | 137 | /// See: [thread()](fn.thread.html) 138 | #[inline(always)] 139 | pub fn thread64() -> u64 { 140 | unsafe { ffi::clock_thread64() } 141 | } 142 | -------------------------------------------------------------------------------- /tarantool/src/ffi/datetime.rs: -------------------------------------------------------------------------------- 1 | pub const MP_DATETIME: i8 = 4; 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Copy, Clone)] 5 | #[allow(non_camel_case_types)] 6 | pub struct datetime { 7 | pub epoch: f64, 8 | pub nsec: i32, 9 | pub tzoffset: i16, 10 | pub tzindex: i16, 11 | } 12 | 13 | crate::define_dlsym_reloc! { 14 | /// Returns the number of bytes required to store a msgpack encoding for `date`. 15 | pub fn tnt_mp_sizeof_datetime(date: *const datetime) -> u32; 16 | 17 | /// Encodes `date` into msgpack, writes the result into the buffer pointed 18 | /// to by `data`, which must have at least `tnt_mp_sizeof_datetime(date)` 19 | /// bytes allocated in it. 20 | /// 21 | /// Returns a pointer to the first byte after the encoded data. 22 | pub fn tnt_mp_encode_datetime(data: *mut u8, date: *const datetime) -> *mut u8; 23 | } 24 | 25 | #[cfg(feature = "internal_test")] 26 | mod test { 27 | use super::*; 28 | use crate::offset_of; 29 | 30 | #[crate::test(tarantool = "crate")] 31 | fn datetime_ffi_definition() { 32 | if !crate::ffi::has_datetime() { 33 | return; 34 | } 35 | 36 | let lua = crate::lua_state(); 37 | let [ 38 | size_of_datetime, 39 | offset_of_epoch, 40 | offset_of_nsec, 41 | offset_of_tzoffset, 42 | offset_of_tzindex, 43 | ]: [usize; 5] = lua.eval( 44 | "local ffi = require 'ffi' 45 | return { 46 | ffi.sizeof('struct datetime'), 47 | ffi.offsetof('struct datetime', 'epoch'), 48 | ffi.offsetof('struct datetime', 'nsec'), 49 | ffi.offsetof('struct datetime', 'tzoffset'), 50 | ffi.offsetof('struct datetime', 'tzindex'), 51 | }", 52 | ).unwrap(); 53 | 54 | // TODO: could also check the actual types of fields using 55 | // `ffi.typeinfo`, but this requires more work 56 | 57 | assert_eq!(size_of_datetime, std::mem::size_of::()); 58 | assert_eq!(offset_of_epoch, offset_of!(datetime, epoch)); 59 | assert_eq!(offset_of_nsec, offset_of!(datetime, nsec)); 60 | assert_eq!(offset_of_tzoffset, offset_of!(datetime, tzoffset)); 61 | assert_eq!(offset_of_tzindex, offset_of!(datetime, tzindex)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tarantool/src/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | #[doc(hidden)] 2 | pub mod helper; 3 | #[doc(hidden)] 4 | pub use ::tlua::ffi as lua; 5 | #[doc(hidden)] 6 | pub mod datetime; 7 | #[doc(hidden)] 8 | pub mod decimal; 9 | #[doc(hidden)] 10 | pub mod sql; 11 | #[doc(hidden)] 12 | pub mod tarantool; 13 | #[doc(hidden)] 14 | pub mod uuid; 15 | 16 | /// Check whether the current tarantool executable supports decimal api. 17 | /// If this function returns `false` using any of the functions in 18 | /// [`tarantool::decimal`] will result in a **panic**. 19 | /// 20 | /// [`tarantool::decimal`]: mod@crate::decimal 21 | pub fn has_decimal() -> bool { 22 | if cfg!(feature = "standalone_decimal") { 23 | return true; 24 | } 25 | 26 | unsafe { 27 | let name = crate::c_str!("decimal_zero"); 28 | helper::tnt_internal_symbol::<*const ()>(name).is_some() || helper::has_dyn_symbol(name) 29 | } 30 | } 31 | 32 | /// Check whether the current tarantool executable supports fiber::channel api. 33 | /// If this function returns `false` using any of the functions in 34 | /// [`tarantool::fiber::channel`] will result in a **panic**. 35 | /// 36 | /// [`tarantool::fiber::channel`]: crate::fiber::channel 37 | pub fn has_fiber_channel() -> bool { 38 | unsafe { 39 | let name = crate::c_str!("fiber_channel_new"); 40 | helper::tnt_internal_symbol::<*const ()>(name).is_some() || helper::has_dyn_symbol(name) 41 | } 42 | } 43 | 44 | /// Check whether the current tarantool executable supports getting tuple fields 45 | /// by json pattern. 46 | /// If this function returns `false` then 47 | /// - passing a string to [`Tuple::try_get`] will always result in an `Error`, 48 | /// - passing a string to [`Tuple::get`] will always result in a **panic**. 49 | /// 50 | /// [`Tuple::try_get`]: crate::tuple::Tuple::try_get 51 | /// [`Tuple::get`]: crate::tuple::Tuple::get 52 | pub fn has_tuple_field_by_path() -> bool { 53 | let c_str = std::ffi::CStr::from_bytes_with_nul_unchecked; 54 | unsafe { 55 | helper::has_dyn_symbol(c_str(tarantool::TUPLE_FIELD_BY_PATH_NEW_API.as_bytes())) 56 | | helper::has_dyn_symbol(c_str(tarantool::TUPLE_FIELD_BY_PATH_OLD_API.as_bytes())) 57 | } 58 | } 59 | 60 | /// Check whether the current tarantool executable supports datetime api. 61 | /// If this function returns `false` using functions in 62 | /// [`tarantool::datetime`] may result in a **panic**. 63 | /// 64 | /// [`tarantool::datetime`]: mod@crate::datetime 65 | pub fn has_datetime() -> bool { 66 | unsafe { helper::has_dyn_symbol(crate::c_str!("tnt_mp_encode_datetime")) } 67 | } 68 | 69 | /// Check whether the current tarantool executable supports fiber_set_ctx api. 70 | /// If this function returns `false` using functions in 71 | /// [`fiber_set_ctx`] & [`fiber_get_ctx`] may result in a **panic**. 72 | /// 73 | /// # Safety 74 | /// This function is only safe to be called from the tx thread. 75 | /// 76 | /// [`fiber_set_ctx`]: crate::ffi::tarantool::fiber_set_ctx 77 | /// [`fiber_get_ctx`]: crate::ffi::tarantool::fiber_get_ctx 78 | #[inline] 79 | pub unsafe fn has_fiber_set_ctx() -> bool { 80 | static mut RESULT: Option = None; 81 | if (*std::ptr::addr_of!(RESULT)).is_none() { 82 | RESULT = Some(helper::has_dyn_symbol(crate::c_str!("fiber_set_ctx"))); 83 | } 84 | RESULT.unwrap() 85 | } 86 | 87 | /// Check whether the current tarantool executable supports the api for 88 | /// fully temporary spaces. 89 | /// 90 | /// If this function returns `false` creating spaces with 91 | /// [`SpaceType::Temporary`] will not work. 92 | /// 93 | /// [`SpaceType::Temporary`]: crate::space::SpaceType::Temporary 94 | #[inline(always)] 95 | pub fn has_fully_temporary_spaces() -> bool { 96 | crate::space::space_id_temporary_min().is_some() 97 | } 98 | 99 | /// Check whether the current tarantool executable supports the [`fiber_find`], 100 | /// [`fiber_set_name_n`], [`fiber_id`], [`fiber_csw`], [`fiber_name`] ffi apis. 101 | /// 102 | /// If this function returns `false` then the corrsponding apis (e.g. setting 103 | /// fiber name) will use the less efficient implementation based on the lua 104 | /// interface. 105 | /// 106 | /// # Safety 107 | /// This function is only safe to be called from the tx thread. 108 | /// 109 | /// [`fiber_find`]: crate::ffi::tarantool::fiber_find 110 | /// [`fiber_set_name_n`]: crate::ffi::tarantool::fiber_set_name_n 111 | /// [`fiber_id`]: crate::ffi::tarantool::fiber_id 112 | /// [`fiber_name`]: crate::ffi::tarantool::fiber_name 113 | /// [`fiber_csw`]: crate::ffi::tarantool::fiber_csw 114 | #[inline] 115 | pub unsafe fn has_fiber_id() -> bool { 116 | static mut RESULT: Option = None; 117 | if (*std::ptr::addr_of!(RESULT)).is_none() { 118 | RESULT = Some(helper::has_dyn_symbol(crate::c_str!("fiber_id"))); 119 | } 120 | RESULT.unwrap() 121 | } 122 | -------------------------------------------------------------------------------- /tarantool/src/ffi/uuid.rs: -------------------------------------------------------------------------------- 1 | pub const MP_UUID: i8 = 2; 2 | 3 | #[repr(C)] 4 | #[derive(Copy, Clone)] 5 | #[allow(non_camel_case_types)] 6 | pub struct tt_uuid { 7 | pub tl: u32, 8 | pub tm: u16, 9 | pub th: u16, 10 | pub csh: u8, 11 | pub csl: u8, 12 | pub n: [u8; 6], 13 | } 14 | 15 | extern "C" { 16 | /// Generate a random uuid (v4) 17 | pub fn tt_uuid_create(uu: *mut tt_uuid); 18 | } 19 | 20 | #[cfg(feature = "internal_test")] 21 | mod test { 22 | use super::*; 23 | use crate::offset_of; 24 | 25 | #[crate::test(tarantool = "crate")] 26 | fn uuid_ffi_definition() { 27 | let lua = crate::lua_state(); 28 | let [ 29 | size_of_uuid, 30 | offset_of_time_low, 31 | offset_of_time_mid, 32 | offset_of_time_hi_and_version, 33 | offset_of_clock_seq_hi_and_reserved, 34 | offset_of_clock_seq_low, 35 | offset_of_node, 36 | ]: [usize; 7] = lua.eval( 37 | "local ffi = require 'ffi' 38 | return { 39 | ffi.sizeof('struct tt_uuid'), 40 | ffi.offsetof('struct tt_uuid', 'time_low'), 41 | ffi.offsetof('struct tt_uuid', 'time_mid'), 42 | ffi.offsetof('struct tt_uuid', 'time_hi_and_version'), 43 | ffi.offsetof('struct tt_uuid', 'clock_seq_hi_and_reserved'), 44 | ffi.offsetof('struct tt_uuid', 'clock_seq_low'), 45 | ffi.offsetof('struct tt_uuid', 'node'), 46 | }", 47 | ).unwrap(); 48 | 49 | // TODO: could also check the actual types of fields using 50 | // `ffi.typeinfo`, but this requires more work 51 | 52 | assert_eq!(size_of_uuid, std::mem::size_of::()); 53 | assert_eq!(offset_of_time_low, offset_of!(tt_uuid, tl)); 54 | assert_eq!(offset_of_time_mid, offset_of!(tt_uuid, tm)); 55 | assert_eq!(offset_of_time_hi_and_version, offset_of!(tt_uuid, th)); 56 | assert_eq!( 57 | offset_of_clock_seq_hi_and_reserved, 58 | offset_of!(tt_uuid, csh) 59 | ); 60 | assert_eq!(offset_of_clock_seq_low, offset_of!(tt_uuid, csl)); 61 | assert_eq!(offset_of_node, offset_of!(tt_uuid, n)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tarantool/src/fiber/csw.rs: -------------------------------------------------------------------------------- 1 | //! Context switches tracking utilities. 2 | //! 3 | //! Those are mostly used for testing. 4 | 5 | use super::FiberId; 6 | 7 | /// Returns the number of context switches of the fiber with given id or of 8 | /// calling fiber if id is `None`. Returns `None` if fiber wasn't found. 9 | pub(crate) fn csw_lua(id: Option) -> Option { 10 | static mut FUNCTION_DEFINED: bool = false; 11 | let lua = crate::lua_state(); 12 | 13 | if unsafe { !FUNCTION_DEFINED } { 14 | #[rustfmt::skip] 15 | lua.exec(r#" 16 | local fiber = require('fiber') 17 | function fiber_csw(id) 18 | local f 19 | if id == nil then 20 | f = fiber.self() 21 | id = f.id() 22 | else 23 | f = fiber.find(id) 24 | end 25 | if f == nil then 26 | return nil 27 | end 28 | if f.csw ~= nil then 29 | return f:csw() 30 | else 31 | return fiber.info({bt = false})[id].csw 32 | end 33 | end 34 | "#).unwrap(); 35 | unsafe { 36 | FUNCTION_DEFINED = true; 37 | } 38 | } 39 | 40 | lua.get::, _>("fiber_csw") 41 | .unwrap() 42 | .into_call_with_args(id) 43 | .unwrap() 44 | } 45 | 46 | /// Calls a function and checks whether it yielded. 47 | /// 48 | /// It's mostly useful in tests. 49 | /// 50 | /// See also: 51 | /// 52 | /// # Examle 53 | /// 54 | /// ```no_run 55 | /// # use tarantool::fiber; 56 | /// # use tarantool::fiber::check_yield; 57 | /// # use tarantool::fiber::YieldResult::*; 58 | /// # use std::time::Duration; 59 | /// assert_eq!( 60 | /// check_yield(|| fiber::sleep(Duration::ZERO)), 61 | /// Yielded(()) 62 | /// ); 63 | /// ``` 64 | pub fn check_yield(f: F) -> YieldResult 65 | where 66 | F: FnOnce() -> T, 67 | { 68 | let csw_before = crate::fiber::csw(); 69 | let res = f(); 70 | if crate::fiber::csw() == csw_before { 71 | YieldResult::DidntYield(res) 72 | } else { 73 | YieldResult::Yielded(res) 74 | } 75 | } 76 | 77 | /// Possible [`check_yield`] results. 78 | #[derive(Debug, PartialEq, Eq)] 79 | pub enum YieldResult { 80 | /// The function didn't yield. 81 | DidntYield(T), 82 | /// The function did yield. 83 | Yielded(T), 84 | } 85 | 86 | #[cfg(feature = "internal_test")] 87 | mod tests { 88 | use super::YieldResult; 89 | use crate::fiber; 90 | use std::time::Duration; 91 | 92 | #[crate::test(tarantool = "crate")] 93 | fn check_yield() { 94 | assert_eq!( 95 | super::check_yield(|| ()), // 96 | YieldResult::DidntYield(()) 97 | ); 98 | assert_eq!( 99 | super::check_yield(|| fiber::sleep(Duration::ZERO)), 100 | YieldResult::Yielded(()) 101 | ); 102 | } 103 | 104 | #[crate::test(tarantool = "crate")] 105 | fn performance() { 106 | let now = crate::time::Instant::now_accurate(); 107 | let _ = crate::fiber::csw(); 108 | let elapsed = now.elapsed(); 109 | print!("{elapsed:?} "); 110 | assert!(elapsed < Duration::from_millis(1)); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tarantool/src/net_box/index.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::vec::IntoIter; 3 | 4 | use crate::error::Error; 5 | use crate::index::IteratorType; 6 | use crate::network::protocol; 7 | use crate::tuple::{Encode, ToTupleBuffer, Tuple}; 8 | 9 | use super::inner::ConnInner; 10 | use super::Options; 11 | 12 | /// Remote index (a group of key values and pointers) 13 | pub struct RemoteIndex { 14 | conn_inner: Rc, 15 | space_id: u32, 16 | index_id: u32, 17 | } 18 | 19 | impl RemoteIndex { 20 | pub(crate) fn new(conn_inner: Rc, space_id: u32, index_id: u32) -> Self { 21 | RemoteIndex { 22 | conn_inner, 23 | space_id, 24 | index_id, 25 | } 26 | } 27 | 28 | /// The remote-call equivalent of the local call `Index::get(...)` 29 | /// (see [details](../index/struct.Index.html#method.get)). 30 | #[inline(always)] 31 | pub fn get(&self, key: &K, options: &Options) -> Result, Error> 32 | where 33 | K: ToTupleBuffer + ?Sized, 34 | { 35 | Ok(self 36 | .select( 37 | IteratorType::Eq, 38 | key, 39 | &Options { 40 | offset: 0, 41 | limit: Some(1), 42 | ..options.clone() 43 | }, 44 | )? 45 | .next()) 46 | } 47 | 48 | /// The remote-call equivalent of the local call `Index::select(...)` 49 | /// (see [details](../index/struct.Index.html#method.select)). 50 | #[inline(always)] 51 | pub fn select( 52 | &self, 53 | iterator_type: IteratorType, 54 | key: &K, 55 | options: &Options, 56 | ) -> Result 57 | where 58 | K: ToTupleBuffer + ?Sized, 59 | { 60 | let rows = self.conn_inner.request( 61 | &protocol::Select { 62 | space_id: self.space_id, 63 | index_id: self.index_id, 64 | limit: options.limit.unwrap_or(u32::MAX), 65 | offset: options.offset, 66 | iterator_type, 67 | key, 68 | }, 69 | options, 70 | )?; 71 | Ok(RemoteIndexIterator { 72 | inner: rows.into_iter(), 73 | }) 74 | } 75 | 76 | /// The remote-call equivalent of the local call `Space::update(...)` 77 | /// (see [details](../index/struct.Index.html#method.update)). 78 | #[inline(always)] 79 | pub fn update( 80 | &self, 81 | key: &K, 82 | ops: &[Op], 83 | options: &Options, 84 | ) -> Result, Error> 85 | where 86 | K: ToTupleBuffer + ?Sized, 87 | Op: Encode, 88 | { 89 | self.conn_inner.request( 90 | &protocol::Update { 91 | space_id: self.space_id, 92 | index_id: self.index_id, 93 | key, 94 | ops, 95 | }, 96 | options, 97 | ) 98 | } 99 | 100 | /// The remote-call equivalent of the local call `Space::upsert(...)` 101 | /// (see [details](../index/struct.Index.html#method.upsert)). 102 | #[inline(always)] 103 | pub fn upsert( 104 | &self, 105 | value: &T, 106 | ops: &[Op], 107 | options: &Options, 108 | ) -> Result, Error> 109 | where 110 | T: ToTupleBuffer + ?Sized, 111 | Op: Encode, 112 | { 113 | self.conn_inner.request( 114 | &protocol::Upsert { 115 | space_id: self.space_id, 116 | index_id: self.index_id, 117 | value, 118 | ops, 119 | }, 120 | options, 121 | ) 122 | } 123 | 124 | /// The remote-call equivalent of the local call `Space::delete(...)` 125 | /// (see [details](../index/struct.Index.html#method.delete)). 126 | pub fn delete(&self, key: &K, options: &Options) -> Result, Error> 127 | where 128 | K: ToTupleBuffer + ?Sized, 129 | { 130 | self.conn_inner.request( 131 | &protocol::Delete { 132 | space_id: self.space_id, 133 | index_id: self.index_id, 134 | key, 135 | }, 136 | options, 137 | ) 138 | } 139 | } 140 | 141 | /// Remote index iterator. Can be used with `for` statement 142 | pub struct RemoteIndexIterator { 143 | inner: IntoIter, 144 | } 145 | 146 | impl Iterator for RemoteIndexIterator { 147 | type Item = Tuple; 148 | 149 | fn next(&mut self) -> Option { 150 | self.inner.next() 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tarantool/src/net_box/options.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::error::Error; 4 | use crate::net_box::Conn; 5 | 6 | /// Most [Conn](struct.Conn.html) methods allows to pass an `options` argument 7 | /// 8 | /// Some options are applicable **only to some** methods (will be ignored otherwise). 9 | /// 10 | /// Which can be: 11 | #[derive(Default, Clone)] 12 | pub struct Options { 13 | /// For example, a method whose `options` argument is `{timeout: Some(Duration::from_secs_f32(1.5)})` will stop 14 | /// after 1.5 seconds on the local node, although this does not guarantee that execution will stop on the remote 15 | /// server node. 16 | pub timeout: Option, 17 | 18 | /// The `offset` option specifies the number of rows to skip before starting to return rows from the query. 19 | /// 20 | /// Can be used with [select()](struct.RemoteIndex.html#method.select) method. 21 | /// Default: `0` 22 | pub offset: u32, 23 | 24 | /// The `limit` option specifies the number of rows to return after the `offset` option has been processed. 25 | /// 26 | /// Can be used with [select()](struct.RemoteIndex.html#method.select) method. 27 | /// Treats as unlimited if `None` specified. 28 | /// Default: `None` 29 | pub limit: Option, 30 | } 31 | 32 | /// Connection options; see [Conn::new()](struct.Conn.html#method.new) 33 | #[derive(Clone)] 34 | pub struct ConnOptions { 35 | /// Authentication user name. If left empty, then the session user is `'guest'` 36 | /// (the `'guest'` user does not need a password). 37 | /// 38 | /// Example: 39 | /// ```no_run 40 | /// use tarantool::net_box::{Conn, ConnOptions}; 41 | /// Conn::new( 42 | /// "localhost:3301", 43 | /// ConnOptions { 44 | /// user: "username".to_string(), 45 | /// password: "userpassword".to_string(), 46 | /// ..ConnOptions::default() 47 | /// }, 48 | /// None 49 | /// ); 50 | /// ``` 51 | pub user: String, 52 | 53 | /// Authentication password. 54 | pub password: String, 55 | 56 | /// Authentication method. Only useful in picodata. 57 | pub auth_method: crate::auth::AuthMethod, 58 | 59 | /// If `reconnect_after` is greater than zero, then a [Conn](struct.Conn.html) instance will try to reconnect if a 60 | /// connection is broken or if a connection attempt fails. 61 | /// 62 | /// This makes transient network failures become transparent to the application. 63 | /// Reconnect happens automatically in the background, so requests that initially fail due to connectivity loss are 64 | /// transparently retried. 65 | /// The number of retries is unlimited, connection attempts are made after each specified interval 66 | /// When a connection is explicitly closed, or when connection object is dropped, then reconnect attempts stop. 67 | pub reconnect_after: Duration, 68 | 69 | /// Duration to wait before returning “error: Connection timed out”. 70 | pub connect_timeout: Duration, 71 | 72 | /// Send buffer flush interval enforced in case of intensive requests stream. 73 | /// 74 | /// Guarantied to be maximum while requests are going. 75 | /// Default: 10ms 76 | pub send_buffer_flush_interval: Duration, 77 | 78 | /// Send buffer soft limit. If limit is reached, fiber will block before buffer flush. 79 | /// 80 | /// Note: This mechanism will prevent buffer overflow in most cases (not at all). In case overflow, buffer 81 | /// reallocation will occurred, which may cause performance issues. 82 | /// Default: 64000 83 | pub send_buffer_limit: usize, 84 | 85 | /// Reallocated capacity of send buffer 86 | /// 87 | /// Default: 65536 88 | pub send_buffer_size: usize, 89 | 90 | /// Reallocated capacity of receive buffer 91 | /// 92 | /// Default: 65536 93 | pub recv_buffer_size: usize, 94 | } 95 | 96 | impl Default for ConnOptions { 97 | fn default() -> Self { 98 | ConnOptions { 99 | user: "".to_string(), 100 | password: "".to_string(), 101 | auth_method: crate::auth::AuthMethod::default(), 102 | reconnect_after: Default::default(), 103 | connect_timeout: Default::default(), 104 | send_buffer_flush_interval: Duration::from_millis(10), 105 | send_buffer_limit: 64000, 106 | send_buffer_size: 65536, 107 | recv_buffer_size: 65536, 108 | } 109 | } 110 | } 111 | 112 | /// Provides triggers for connect, disconnect and schema reload events. 113 | pub trait ConnTriggers { 114 | /// Defines a trigger for execution when a new connection is established, and authentication and schema fetch are 115 | /// completed due to an event such as `connect`. 116 | /// 117 | /// If the trigger execution fails and an exception happens, the connection’s state changes to `error`. In this 118 | /// case, the connection is terminated. 119 | fn on_connect(&self, conn: &Conn) -> Result<(), Error>; 120 | 121 | /// Define a trigger for execution after a connection is closed. 122 | fn on_disconnect(&self); 123 | 124 | /// Define a trigger executed when some operation has been performed on the remote server after schema has been 125 | /// updated. So, if a server request fails due to a schema version mismatch error, schema reload is triggered. 126 | fn on_schema_reload(&self, conn: &Conn); 127 | } 128 | -------------------------------------------------------------------------------- /tarantool/src/net_box/schema.rs: -------------------------------------------------------------------------------- 1 | use once_cell::unsync::Lazy; 2 | 3 | use std::cell::{Cell, RefCell}; 4 | use std::collections::HashMap; 5 | use std::net::SocketAddr; 6 | use std::rc::Rc; 7 | 8 | use crate::error::Error; 9 | use crate::fiber::{Latch, LatchGuard}; 10 | use crate::index::{self, IteratorType}; 11 | use crate::network::protocol; 12 | use crate::space::{self, SystemSpace, SYSTEM_ID_MAX}; 13 | use crate::tuple::Tuple; 14 | 15 | use super::inner::ConnInner; 16 | use super::options::Options; 17 | 18 | pub struct ConnSchema { 19 | version: Cell>, 20 | is_updating: Cell, 21 | space_ids: RefCell>, 22 | index_ids: RefCell>, 23 | lock: Latch, 24 | } 25 | 26 | impl ConnSchema { 27 | pub fn acquire(addrs: &[SocketAddr]) -> Rc { 28 | let addr = SCHEMA_CACHE.with(|cache| { 29 | let cache = cache.cache.borrow(); 30 | addrs.iter().find_map(|addr| cache.get(addr).cloned()) 31 | }); 32 | if let Some(addr) = addr { 33 | return addr; 34 | } 35 | 36 | let schema = Rc::new(ConnSchema { 37 | version: Cell::new(None), 38 | is_updating: Cell::new(false), 39 | space_ids: Default::default(), 40 | index_ids: Default::default(), 41 | lock: Latch::new(), 42 | }); 43 | 44 | SCHEMA_CACHE.with(|cache| { 45 | let mut cache = cache.cache.borrow_mut(); 46 | for addr in addrs { 47 | cache.insert(*addr, schema.clone()); 48 | } 49 | }); 50 | 51 | schema 52 | } 53 | 54 | pub fn refresh( 55 | &self, 56 | conn_inner: &Rc, 57 | actual_version: Option, 58 | ) -> Result { 59 | let mut _lock: Option = None; 60 | 61 | if self.is_updating.get() { 62 | _lock = Some(self.lock.lock()); 63 | } 64 | 65 | let result = if self.is_outdated(actual_version) { 66 | if _lock.is_none() { 67 | _lock = Some(self.lock.lock()); 68 | } 69 | 70 | self.update(conn_inner)?; 71 | true 72 | } else { 73 | false 74 | }; 75 | Ok(result) 76 | } 77 | 78 | pub fn update(&self, conn_inner: &Rc) -> Result<(), Error> { 79 | self.is_updating.set(true); 80 | let (spaces_data, actual_schema_version) = self.fetch_schema_spaces(conn_inner)?; 81 | for row in spaces_data { 82 | let metadata = row.decode::()?; 83 | self.space_ids 84 | .borrow_mut() 85 | .insert(metadata.name.to_string(), metadata.id); 86 | } 87 | 88 | for row in self.fetch_schema_indexes(conn_inner)? { 89 | let metadata = row.decode::()?; 90 | self.index_ids.borrow_mut().insert( 91 | (metadata.space_id, metadata.name.to_string()), 92 | metadata.index_id, 93 | ); 94 | } 95 | 96 | self.version.set(Some(actual_schema_version)); 97 | self.is_updating.set(false); 98 | Ok(()) 99 | } 100 | 101 | pub fn lookup_space(&self, name: &str) -> Option { 102 | self.space_ids.borrow().get(name).copied() 103 | } 104 | 105 | pub fn lookup_index(&self, name: &str, space_id: u32) -> Option { 106 | self.index_ids 107 | .borrow() 108 | .get(&(space_id, name.to_string())) 109 | .copied() 110 | } 111 | 112 | fn is_outdated(&self, actual_version: Option) -> bool { 113 | match actual_version { 114 | None => true, 115 | Some(actual_version) => match self.version.get() { 116 | None => true, 117 | Some(cached_version) => actual_version > cached_version, 118 | }, 119 | } 120 | } 121 | 122 | #[inline(always)] 123 | fn fetch_schema_spaces(&self, conn_inner: &Rc) -> Result<(Vec, u64), Error> { 124 | let rows = conn_inner.request( 125 | &protocol::Select { 126 | space_id: SystemSpace::VSpace as u32, 127 | index_id: 0, 128 | limit: u32::MAX, 129 | offset: 0, 130 | iterator_type: IteratorType::GT, 131 | key: &(SYSTEM_ID_MAX,), 132 | }, 133 | &Options::default(), 134 | )?; 135 | let schema_version = conn_inner 136 | .schema_version 137 | .get() 138 | .expect("should be present after we received a response"); 139 | Ok((rows, schema_version)) 140 | } 141 | 142 | #[inline(always)] 143 | fn fetch_schema_indexes(&self, conn_inner: &Rc) -> Result, Error> { 144 | conn_inner.request( 145 | &protocol::Select { 146 | space_id: SystemSpace::VIndex as u32, 147 | index_id: 0, 148 | limit: u32::MAX, 149 | offset: 0, 150 | iterator_type: IteratorType::All, 151 | key: &(), 152 | }, 153 | &Options::default(), 154 | ) 155 | } 156 | } 157 | 158 | struct ConnSchemaCache { 159 | cache: RefCell>>, 160 | } 161 | 162 | unsafe impl Sync for ConnSchemaCache {} 163 | 164 | thread_local! { 165 | static SCHEMA_CACHE: Lazy = Lazy::new(|| 166 | ConnSchemaCache { 167 | cache: RefCell::new(HashMap::new()), 168 | } 169 | ) 170 | } 171 | -------------------------------------------------------------------------------- /tarantool/src/net_box/send_queue.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use std::io::{self, Cursor, Write}; 3 | use std::time::{Duration, SystemTime}; 4 | 5 | use crate::error::Error; 6 | use crate::fiber::{reschedule, Cond}; 7 | use crate::network::protocol; 8 | use crate::network::protocol::SyncIndex; 9 | 10 | pub struct SendQueue { 11 | is_active: Cell, 12 | sync: Cell, 13 | front_buffer: RefCell>>, 14 | back_buffer: RefCell>>, 15 | swap_cond: Cond, 16 | buffer_limit: u64, 17 | flush_interval: Duration, 18 | } 19 | 20 | impl SendQueue { 21 | pub fn new(buffer_size: usize, buffer_limit: usize, flush_interval: Duration) -> Self { 22 | SendQueue { 23 | is_active: Cell::new(true), 24 | sync: Cell::new(SyncIndex(0)), 25 | front_buffer: RefCell::new(Cursor::new(Vec::with_capacity(buffer_size))), 26 | back_buffer: RefCell::new(Cursor::new(Vec::with_capacity(buffer_size))), 27 | swap_cond: Cond::new(), 28 | buffer_limit: buffer_limit as u64, 29 | flush_interval, 30 | } 31 | } 32 | 33 | pub fn send(&self, request: &R) -> Result 34 | where 35 | R: protocol::Request, 36 | { 37 | let sync = self.next_sync(); 38 | 39 | if self.back_buffer.borrow().position() >= self.buffer_limit { 40 | self.swap_cond.signal(); 41 | } 42 | 43 | let mut buffer = self.back_buffer.borrow_mut(); 44 | // Convert cursor type `Cursor>` -> `Cursor<&mut Vec>` 45 | let msg_start_offset = buffer.position(); 46 | let mut adapted_buffer = Cursor::new(buffer.get_mut()); 47 | adapted_buffer.set_position(msg_start_offset); 48 | 49 | protocol::write_to_buffer(&mut adapted_buffer, sync, request)?; 50 | 51 | // Advance the shared cursor's position, 52 | // because now only the adapted one knows the correct position 53 | let new_offset = adapted_buffer.position(); 54 | buffer.set_position(new_offset); 55 | 56 | // trigger swap condition if buffer was empty before 57 | if msg_start_offset == 0 { 58 | self.swap_cond.signal(); 59 | } 60 | 61 | Ok(sync) 62 | } 63 | 64 | pub fn next_sync(&self) -> SyncIndex { 65 | let sync = self.sync.get(); 66 | self.sync.set(SyncIndex(sync.0 + 1)); 67 | sync 68 | } 69 | 70 | pub fn flush_to_stream(&self, stream: &mut impl Write) -> io::Result<()> { 71 | let start_ts = SystemTime::now(); 72 | let mut prev_data_size = 0u64; 73 | 74 | loop { 75 | if !self.is_active.get() { 76 | return Err(io::Error::from(io::ErrorKind::TimedOut)); 77 | } 78 | 79 | let data_size = self.back_buffer.borrow().position(); 80 | if data_size == 0 { 81 | // await for data (if buffer is empty) 82 | self.swap_cond.wait(); 83 | continue; 84 | } 85 | 86 | if let Ok(elapsed) = start_ts.elapsed() { 87 | if data_size > prev_data_size && elapsed <= self.flush_interval { 88 | prev_data_size = data_size; 89 | reschedule(); 90 | continue; 91 | } 92 | } 93 | 94 | self.back_buffer.swap(&self.front_buffer); 95 | break; 96 | } 97 | 98 | // write front buffer contents to stream + clear front buffer 99 | let mut buffer = self.front_buffer.borrow_mut(); 100 | stream.write_all(buffer.get_ref())?; 101 | buffer.set_position(0); 102 | buffer.get_mut().clear(); 103 | Ok(()) 104 | } 105 | 106 | pub fn close(&self) { 107 | self.is_active.set(false); 108 | self.swap_cond.signal(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tarantool/src/net_box/space.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::error::Error; 4 | use crate::index::IteratorType; 5 | use crate::tuple::{Encode, ToTupleBuffer, Tuple}; 6 | 7 | use super::index::{RemoteIndex, RemoteIndexIterator}; 8 | use super::inner::ConnInner; 9 | use super::options::Options; 10 | use super::protocol; 11 | 12 | /// Remote space 13 | pub struct RemoteSpace { 14 | conn_inner: Rc, 15 | space_id: u32, 16 | } 17 | 18 | impl RemoteSpace { 19 | #[inline(always)] 20 | pub(crate) fn new(conn_inner: Rc, space_id: u32) -> Self { 21 | RemoteSpace { 22 | conn_inner, 23 | space_id, 24 | } 25 | } 26 | 27 | /// Find index by name (on remote space) 28 | pub fn index(&self, name: &str) -> Result, Error> { 29 | Ok(self 30 | .conn_inner 31 | .lookup_index(name, self.space_id)? 32 | .map(|index_id| RemoteIndex::new(self.conn_inner.clone(), self.space_id, index_id))) 33 | } 34 | 35 | /// Returns index with id = 0 36 | #[inline(always)] 37 | pub fn primary_key(&self) -> RemoteIndex { 38 | RemoteIndex::new(self.conn_inner.clone(), self.space_id, 0) 39 | } 40 | 41 | /// The remote-call equivalent of the local call `Space::get(...)` 42 | /// (see [details](../space/struct.Space.html#method.get)). 43 | #[inline(always)] 44 | pub fn get(&self, key: &K, options: &Options) -> Result, Error> 45 | where 46 | K: ToTupleBuffer + ?Sized, 47 | { 48 | self.primary_key().get(key, options) 49 | } 50 | 51 | /// The remote-call equivalent of the local call `Space::select(...)` 52 | /// (see [details](../space/struct.Space.html#method.select)). 53 | #[inline(always)] 54 | pub fn select( 55 | &self, 56 | iterator_type: IteratorType, 57 | key: &K, 58 | options: &Options, 59 | ) -> Result 60 | where 61 | K: ToTupleBuffer + ?Sized, 62 | { 63 | self.primary_key().select(iterator_type, key, options) 64 | } 65 | 66 | /// The remote-call equivalent of the local call `Space::insert(...)` 67 | /// (see [details](../space/struct.Space.html#method.insert)). 68 | #[inline(always)] 69 | pub fn insert(&self, value: &T, options: &Options) -> Result, Error> 70 | where 71 | T: ToTupleBuffer + ?Sized, 72 | { 73 | self.conn_inner.request( 74 | &protocol::Insert { 75 | space_id: self.space_id, 76 | value, 77 | }, 78 | options, 79 | ) 80 | } 81 | 82 | /// The remote-call equivalent of the local call `Space::replace(...)` 83 | /// (see [details](../space/struct.Space.html#method.replace)). 84 | #[inline(always)] 85 | pub fn replace(&self, value: &T, options: &Options) -> Result, Error> 86 | where 87 | T: ToTupleBuffer + ?Sized, 88 | { 89 | self.conn_inner.request( 90 | &protocol::Replace { 91 | space_id: self.space_id, 92 | value, 93 | }, 94 | options, 95 | ) 96 | } 97 | 98 | /// The remote-call equivalent of the local call `Space::update(...)` 99 | /// (see [details](../space/struct.Space.html#method.update)). 100 | #[inline(always)] 101 | pub fn update( 102 | &self, 103 | key: &K, 104 | ops: &[Op], 105 | options: &Options, 106 | ) -> Result, Error> 107 | where 108 | K: ToTupleBuffer + ?Sized, 109 | Op: Encode, 110 | { 111 | self.primary_key().update(key, ops, options) 112 | } 113 | 114 | /// The remote-call equivalent of the local call `Space::upsert(...)` 115 | /// (see [details](../space/struct.Space.html#method.upsert)). 116 | #[inline(always)] 117 | pub fn upsert( 118 | &self, 119 | value: &T, 120 | ops: &[Op], 121 | options: &Options, 122 | ) -> Result, Error> 123 | where 124 | T: ToTupleBuffer + ?Sized, 125 | Op: Encode, 126 | { 127 | self.primary_key().upsert(value, ops, options) 128 | } 129 | 130 | /// The remote-call equivalent of the local call `Space::delete(...)` 131 | /// (see [details](../space/struct.Space.html#method.delete)). 132 | #[inline(always)] 133 | pub fn delete(&self, key: &K, options: &Options) -> Result, Error> 134 | where 135 | K: ToTupleBuffer + ?Sized, 136 | { 137 | self.primary_key().delete(key, options) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tarantool/src/net_box/stream.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::io::{self, Read, Write}; 3 | use std::os::unix::io::{IntoRawFd, RawFd}; 4 | use std::rc::Rc; 5 | 6 | use crate::coio::{read, write, CoIOStream}; 7 | use crate::error::Error; 8 | use crate::ffi::tarantool as ffi; 9 | use crate::fiber::Cond; 10 | 11 | pub struct ConnStream { 12 | fd: RawFd, 13 | reader_guard: Rc, 14 | writer_guard: Rc, 15 | } 16 | 17 | impl ConnStream { 18 | pub fn new(stream: CoIOStream) -> Result { 19 | Ok(ConnStream { 20 | fd: stream.into_raw_fd(), 21 | reader_guard: Rc::new(ConnStreamGuard { 22 | is_acquired: Cell::new(false), 23 | drop_cond: Cond::new(), 24 | }), 25 | writer_guard: Rc::new(ConnStreamGuard { 26 | is_acquired: Cell::new(false), 27 | drop_cond: Cond::new(), 28 | }), 29 | }) 30 | } 31 | 32 | pub fn is_reader_acquired(&self) -> bool { 33 | self.reader_guard.is_acquired.get() 34 | } 35 | 36 | pub fn acquire_reader(&self) -> ConnStreamReader { 37 | self.reader_guard.wait(); 38 | self.reader_guard.is_acquired.set(true); 39 | ConnStreamReader { 40 | fd: self.fd, 41 | reader_guard: self.reader_guard.clone(), 42 | } 43 | } 44 | 45 | pub fn acquire_writer(&self) -> ConnStreamWriter { 46 | self.writer_guard.wait(); 47 | self.writer_guard.is_acquired.set(true); 48 | ConnStreamWriter { 49 | fd: self.fd, 50 | writer_guard: self.writer_guard.clone(), 51 | } 52 | } 53 | } 54 | 55 | struct ConnStreamGuard { 56 | is_acquired: Cell, 57 | drop_cond: Cond, 58 | } 59 | 60 | impl ConnStreamGuard { 61 | fn wait(&self) { 62 | if self.is_acquired.get() { 63 | self.drop_cond.wait(); 64 | } 65 | } 66 | } 67 | 68 | impl Drop for ConnStream { 69 | fn drop(&mut self) { 70 | self.reader_guard.wait(); 71 | self.writer_guard.wait(); 72 | unsafe { ffi::coio_close(self.fd) }; 73 | } 74 | } 75 | 76 | pub struct ConnStreamReader { 77 | fd: RawFd, 78 | reader_guard: Rc, 79 | } 80 | 81 | impl Read for ConnStreamReader { 82 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 83 | read(self.fd, buf, None) 84 | } 85 | } 86 | 87 | impl Drop for ConnStreamReader { 88 | fn drop(&mut self) { 89 | self.reader_guard.is_acquired.set(false); 90 | self.reader_guard.drop_cond.signal(); 91 | } 92 | } 93 | 94 | pub struct ConnStreamWriter { 95 | fd: RawFd, 96 | writer_guard: Rc, 97 | } 98 | 99 | impl Write for ConnStreamWriter { 100 | fn write(&mut self, buf: &[u8]) -> io::Result { 101 | write(self.fd, buf, None) 102 | } 103 | 104 | fn flush(&mut self) -> io::Result<()> { 105 | Ok(()) 106 | } 107 | } 108 | 109 | impl Drop for ConnStreamWriter { 110 | fn drop(&mut self) { 111 | self.writer_guard.is_acquired.set(false); 112 | self.writer_guard.drop_cond.signal(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tarantool/src/network/client/stream.rs: -------------------------------------------------------------------------------- 1 | use super::tcp::TcpStream; 2 | use super::tls::TlsStream; 3 | use futures::{AsyncRead, AsyncWrite}; 4 | use std::io; 5 | use std::pin::Pin; 6 | use std::task::{Context, Poll}; 7 | 8 | /// An asynchronous network stream that supports both plain and encrypted connections. 9 | /// 10 | /// This enum abstracts away the differences between plain TCP streams and 11 | /// TLS-encrypted streams, allowing unified handling of both connection types. 12 | #[derive(Debug, Clone)] 13 | pub enum Stream { 14 | Plain(TcpStream), 15 | Secure(TlsStream), 16 | } 17 | 18 | impl Stream { 19 | pub fn shutdown(&self) -> io::Result<()> { 20 | match self { 21 | Self::Plain(tcp) => tcp.close(), 22 | Self::Secure(tls) => tls.shutdown(), 23 | } 24 | } 25 | } 26 | 27 | impl AsyncWrite for Stream { 28 | fn poll_write( 29 | self: Pin<&mut Self>, 30 | cx: &mut Context<'_>, 31 | buf: &[u8], 32 | ) -> Poll> { 33 | match self.get_mut() { 34 | Self::Plain(tcp) => Pin::new(tcp).poll_write(cx, buf), 35 | Self::Secure(tls) => Pin::new(tls).poll_write(cx, buf), 36 | } 37 | } 38 | 39 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 40 | match self.get_mut() { 41 | Self::Plain(tcp) => Pin::new(tcp).poll_flush(cx), 42 | Self::Secure(tls) => Pin::new(tls).poll_flush(cx), 43 | } 44 | } 45 | 46 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 47 | match self.get_mut() { 48 | Self::Plain(tcp) => Pin::new(tcp).poll_close(cx), 49 | Self::Secure(tls) => Pin::new(tls).poll_close(cx), 50 | } 51 | } 52 | } 53 | 54 | impl AsyncRead for Stream { 55 | fn poll_read( 56 | self: Pin<&mut Self>, 57 | cx: &mut Context<'_>, 58 | buf: &mut [u8], 59 | ) -> Poll> { 60 | match self.get_mut() { 61 | Self::Plain(tcp) => Pin::new(tcp).poll_read(cx, buf), 62 | Self::Secure(tls) => Pin::new(tls).poll_read(cx, buf), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tarantool/src/network/client/tls.rs: -------------------------------------------------------------------------------- 1 | //! Contains an implementation of a custom async coio based [`TlsStream`]. 2 | //! 3 | //! [`TlsStream`] is an asynchronous wrapper around [`ssl::SslStream`] 4 | //! for use with Rust's async/await functionality. 5 | //! 6 | //! Unlike [`ssl::SslStream`], which only works with synchronous `S: Read + Write` types, 7 | //! [`TlsStream`] implements async traits. The underlying [`TcpStream`] implements both [`Read`] 8 | //! and [`Write`] to enable [`ssl::SslStream`] to function. Internally, [`TcpStream`] 9 | //! uses non-blocking sockets. 10 | 11 | use super::tcp::TcpStream; 12 | use crate::ffi::tarantool as ffi; 13 | use crate::fiber::r#async::context::ContextExt; 14 | use futures::{AsyncRead, AsyncWrite}; 15 | use openssl::{ssl, x509}; 16 | use std::cell::RefCell; 17 | use std::future; 18 | use std::io; 19 | use std::io::{Read, Write}; 20 | use std::path::PathBuf; 21 | use std::pin::Pin; 22 | use std::rc::Rc; 23 | use std::task::{Context, Poll}; 24 | 25 | pub struct TlsConfig<'a> { 26 | pub cert_file: &'a PathBuf, 27 | pub key_file: &'a PathBuf, 28 | pub ca_file: Option<&'a PathBuf>, 29 | } 30 | 31 | /// Wrapper around [`ssl::SslConnector`] that configures TLS settings. 32 | #[derive(Debug, Clone)] 33 | pub struct TlsConnector(ssl::SslConnector); 34 | 35 | impl TlsConnector { 36 | pub fn new(config: TlsConfig) -> io::Result { 37 | let mut builder = ssl::SslConnector::builder(ssl::SslMethod::tls())?; 38 | builder.set_verify(ssl::SslVerifyMode::PEER); 39 | builder.set_certificate_file(config.cert_file, ssl::SslFiletype::PEM)?; 40 | builder.set_private_key_file(config.key_file, ssl::SslFiletype::PEM)?; 41 | 42 | if let Some(ca_file) = config.ca_file { 43 | let pem = std::fs::read(ca_file)?; 44 | let certs = x509::X509::stack_from_pem(&pem)?; 45 | let mut store_builder = x509::store::X509StoreBuilder::new()?; 46 | certs 47 | .into_iter() 48 | .try_for_each(|c| store_builder.add_cert(c))?; 49 | builder.set_verify_cert_store(store_builder.build())?; 50 | } 51 | 52 | Ok(Self(builder.build())) 53 | } 54 | 55 | pub fn connect( 56 | &self, 57 | stream: TcpStream, 58 | domain: &str, 59 | ) -> Result, ssl::HandshakeError> { 60 | self.0.connect(domain, stream) 61 | } 62 | } 63 | 64 | /// An asynchronous wrapper around [`ssl::SslStream`] 65 | /// (to use with Rust async/await). 66 | #[derive(Debug, Clone)] 67 | pub struct TlsStream { 68 | inner: Rc>>, 69 | } 70 | 71 | impl TlsStream { 72 | pub async fn connect( 73 | connector: &TlsConnector, 74 | stream: TcpStream, 75 | domain: &str, 76 | ) -> io::Result { 77 | let fd = stream.fd()?; 78 | 79 | let res = connector.connect(stream, domain); 80 | let mut mid_handshake_ssl_stream = match res { 81 | Ok(stream) => { 82 | return Ok(Self { 83 | inner: Rc::new(RefCell::new(stream)), 84 | }); 85 | } 86 | Err(ssl::HandshakeError::WouldBlock(m)) => Some(m), 87 | Err(e) => return Err(io::Error::other(e)), 88 | }; 89 | 90 | let stream = future::poll_fn(|cx| { 91 | let mid = mid_handshake_ssl_stream 92 | .take() 93 | .expect("taken once per poll"); 94 | match mid.handshake() { 95 | Ok(stream) => Poll::Ready(Ok(stream)), 96 | Err(ssl::HandshakeError::WouldBlock(next_mid)) => { 97 | let event = if next_mid.error().code() == ssl::ErrorCode::WANT_READ { 98 | ffi::CoIOFlags::READ 99 | } else { 100 | ffi::CoIOFlags::WRITE 101 | }; 102 | mid_handshake_ssl_stream = Some(next_mid); 103 | // SAFETY: safe as long as this future is executed by `fiber::block_on` async executor. 104 | unsafe { 105 | ContextExt::set_coio_wait(cx, fd, event); 106 | } 107 | Poll::Pending 108 | } 109 | Err(e) => Poll::Ready(Err(io::Error::other(e))), 110 | } 111 | }) 112 | .await?; 113 | 114 | Ok(Self { 115 | inner: Rc::new(RefCell::new(stream)), 116 | }) 117 | } 118 | 119 | pub fn shutdown(&self) -> io::Result<()> { 120 | self.inner 121 | .borrow_mut() 122 | .shutdown() 123 | .map_err(|e| e.into_io_error().unwrap_or_else(io::Error::other))?; 124 | self.inner.borrow().get_ref().close() 125 | } 126 | } 127 | 128 | impl AsyncWrite for TlsStream { 129 | fn poll_write( 130 | self: Pin<&mut Self>, 131 | cx: &mut Context<'_>, 132 | buf: &[u8], 133 | ) -> Poll> { 134 | let this = self.get_mut(); 135 | let result = this.inner.borrow_mut().write(buf); 136 | match result { 137 | Ok(num) => Poll::Ready(Ok(num)), 138 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 139 | let raw_fd = this.inner.borrow().get_ref().fd()?; 140 | // SAFETY: safe as long as this future is executed by `fiber::block_on` async executor. 141 | unsafe { 142 | ContextExt::set_coio_wait(cx, raw_fd, ffi::CoIOFlags::WRITE); 143 | } 144 | Poll::Pending 145 | } 146 | Err(e) => Poll::Ready(Err(e)), 147 | } 148 | } 149 | 150 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 151 | Poll::Ready(Ok(())) 152 | } 153 | 154 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 155 | let this = self.get_mut(); 156 | Poll::Ready(this.shutdown()) 157 | } 158 | } 159 | 160 | impl AsyncRead for TlsStream { 161 | fn poll_read( 162 | self: Pin<&mut Self>, 163 | cx: &mut Context<'_>, 164 | buf: &mut [u8], 165 | ) -> Poll> { 166 | let this = self.get_mut(); 167 | let result = this.inner.borrow_mut().read(buf); 168 | match result { 169 | Ok(num) => Poll::Ready(Ok(num)), 170 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 171 | let raw_fd = this.inner.borrow().get_ref().fd()?; 172 | // SAFETY: safe as long as this future is executed by `fiber::block_on` async executor. 173 | unsafe { 174 | ContextExt::set_coio_wait(cx, raw_fd, ffi::CoIOFlags::READ); 175 | } 176 | Poll::Pending 177 | } 178 | Err(e) => Poll::Ready(Err(e)), 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tarantool/src/network/mod.rs: -------------------------------------------------------------------------------- 1 | //! Sans-I/O network client for connecting to remote Tarantool server. 2 | //! 3 | //! Consists of: 4 | //! - Runtime and transport agnostic [`protocol`] layer 5 | //! - Async and coio based [`client`] layer 6 | //! 7 | //! More on Sans-I/O pattern can be found on the respective [wiki](https://sans-io.readthedocs.io/how-to-sans-io.html). 8 | //! 9 | //! This client implementation is not yet as feature rich as [`super::net_box`]. 10 | //! Though it is in active development and should eventually replace net_box. 11 | 12 | #[cfg(feature = "network_client")] 13 | pub mod client; 14 | pub mod protocol; 15 | 16 | pub use protocol::ProtocolError; 17 | 18 | #[cfg(feature = "network_client")] 19 | pub use client::reconnect::Client as ReconnClient; 20 | #[cfg(feature = "network_client")] 21 | pub use client::{AsClient, Client, ClientError}; 22 | pub use protocol::Config; 23 | 24 | #[cfg(feature = "network_client")] 25 | #[deprecated = "use `ClientError` instead"] 26 | pub type Error = client::ClientError; 27 | 28 | #[cfg(feature = "internal_test")] 29 | #[cfg(feature = "network_client")] 30 | mod tests { 31 | use super::*; 32 | use crate::test::util::listen_port; 33 | 34 | #[crate::test(tarantool = "crate")] 35 | async fn wrong_credentials() { 36 | // Wrong user 37 | { 38 | let mut config = Config::default(); 39 | config.creds = Some(("no such user".into(), "password".into())); 40 | let client = ReconnClient::with_config("localhost".into(), listen_port(), config); 41 | 42 | let err = client.ping().await.unwrap_err(); 43 | #[rustfmt::skip] 44 | assert_eq!(err.to_string(), "server responded with error: PasswordMismatch: User not found or supplied credentials are invalid"); 45 | } 46 | 47 | // Wrong password 48 | { 49 | let mut config = Config::default(); 50 | config.creds = Some(("test_user".into(), "wrong password".into())); 51 | let client = ReconnClient::with_config("localhost".into(), listen_port(), config); 52 | 53 | let err = client.ping().await.unwrap_err(); 54 | #[rustfmt::skip] 55 | assert_eq!(err.to_string(), "server responded with error: PasswordMismatch: User not found or supplied credentials are invalid"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tarantool/src/read_view.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi::tarantool as ffi; 2 | use crate::index::IndexId; 3 | use crate::space::SpaceId; 4 | use std::mem::align_of; 5 | use std::mem::size_of; 6 | use std::mem::MaybeUninit; 7 | use std::ptr::NonNull; 8 | 9 | //////////////////////////////////////////////////////////////////////////////// 10 | // read view 11 | //////////////////////////////////////////////////////////////////////////////// 12 | 13 | /// An object which guards a read view on the selected set of spaces and 14 | /// indexes. Provides access to the frozen contents of the selected indexes at 15 | /// the moment of the read view's creation. 16 | #[derive(Debug, PartialEq, Eq, Hash)] 17 | pub struct ReadView { 18 | inner: NonNull, 19 | space_indexes: Vec<(SpaceId, IndexId)>, 20 | } 21 | 22 | impl ReadView { 23 | /// Open a read view on the given space indexes. 24 | #[inline] 25 | #[track_caller] 26 | pub fn for_space_indexes(space_indexes: Vec<(SpaceId, IndexId)>) -> crate::Result { 27 | const _: () = { 28 | assert!(size_of::<(SpaceId, IndexId)>() == size_of::()); 29 | assert!(align_of::<(SpaceId, IndexId)>() == align_of::()); 30 | }; 31 | 32 | let loc = std::panic::Location::caller(); 33 | let name = format!("\0", loc.file(), loc.line()); 34 | let rv = unsafe { 35 | ffi::box_read_view_open_for_given_spaces( 36 | name.as_ptr() as _, 37 | space_indexes.as_ptr() as _, 38 | space_indexes.len() as _, 39 | 0, 40 | ) 41 | }; 42 | 43 | let Some(rv) = NonNull::new(rv) else { 44 | return Err(crate::error::TarantoolError::last().into()); 45 | }; 46 | 47 | Ok(Self { 48 | inner: rv, 49 | space_indexes, 50 | }) 51 | } 52 | 53 | /// Get the list of spaces and idexes for which the read view was opened, if 54 | /// it's available. 55 | #[inline(always)] 56 | pub fn space_indexes(&self) -> Option<&[(SpaceId, IndexId)]> { 57 | // NOTE: Currently the data is always available but in the future we 58 | // may add other ways of specifying spaces for the read view (e.g. all 59 | // spaces, etc.). In that case the slice of space & index ids would not 60 | // be available and this function would return None. 61 | Some(&self.space_indexes) 62 | } 63 | 64 | /// Get an iterator over all of the tuples in the given index read view. 65 | /// The tuples are returned as raw byte slices. 66 | #[inline] 67 | pub fn iter_all( 68 | &self, 69 | space: SpaceId, 70 | index: IndexId, 71 | ) -> crate::Result>> { 72 | unsafe { 73 | let mut iter = MaybeUninit::uninit(); 74 | let rc = ffi::box_read_view_iterator_all( 75 | self.inner.as_ptr(), 76 | space, 77 | index, 78 | iter.as_mut_ptr(), 79 | ); 80 | if rc != 0 { 81 | return Err(crate::error::TarantoolError::last().into()); 82 | } 83 | let iter = iter.assume_init(); 84 | Ok(NonNull::new(iter).map(|inner| ReadViewIterator { 85 | inner, 86 | _marker: std::marker::PhantomData, 87 | })) 88 | } 89 | } 90 | } 91 | 92 | impl Drop for ReadView { 93 | #[inline(always)] 94 | fn drop(&mut self) { 95 | unsafe { ffi::box_read_view_close(self.inner.as_ptr()) } 96 | } 97 | } 98 | 99 | #[derive(Debug, PartialEq, Eq, Hash)] 100 | pub struct ReadViewIterator<'a> { 101 | inner: NonNull, 102 | _marker: std::marker::PhantomData<&'a ()>, 103 | } 104 | 105 | impl<'a> Iterator for ReadViewIterator<'a> { 106 | type Item = &'a [u8]; 107 | 108 | /// Get next tuple data. 109 | #[inline] 110 | fn next(&mut self) -> Option { 111 | let mut data = MaybeUninit::uninit(); 112 | let mut size = MaybeUninit::uninit(); 113 | unsafe { 114 | let rc = ffi::box_read_view_iterator_next_raw( 115 | self.inner.as_ptr(), 116 | data.as_mut_ptr(), 117 | size.as_mut_ptr(), 118 | ); 119 | if rc != 0 { 120 | return None; 121 | } 122 | let data = data.assume_init(); 123 | let size = size.assume_init(); 124 | if data.is_null() { 125 | return None; 126 | } 127 | Some(std::slice::from_raw_parts(data, size as _)) 128 | } 129 | } 130 | } 131 | 132 | impl Drop for ReadViewIterator<'_> { 133 | #[inline(always)] 134 | fn drop(&mut self) { 135 | unsafe { ffi::box_read_view_iterator_free(self.inner.as_ptr()) } 136 | } 137 | } 138 | 139 | #[cfg(feature = "internal_test")] 140 | mod tests { 141 | use super::*; 142 | use crate::space::Space; 143 | use crate::space::SystemSpace; 144 | use crate::temp_space_name; 145 | 146 | #[crate::test(tarantool = "crate")] 147 | fn read_view() { 148 | let s = Space::builder(&temp_space_name!()).create().unwrap(); 149 | s.index_builder("pk").create().unwrap(); 150 | s.insert(&(1, 2, 3)).unwrap(); 151 | s.insert(&(2, "hello")).unwrap(); 152 | 153 | let rv = ReadView::for_space_indexes(vec![(s.id(), 0)]).unwrap(); 154 | 155 | // Space is not in the read view. 156 | assert_eq!(rv.iter_all(SystemSpace::Space as _, 0).unwrap(), None); 157 | 158 | let mut iter = rv.iter_all(s.id(), 0).unwrap().unwrap(); 159 | assert_eq!(iter.next(), Some(&b"\x93\x01\x02\x03"[..])); 160 | assert_eq!(iter.next(), Some(&b"\x92\x02\xa5hello"[..])); 161 | assert_eq!(iter.next(), None); 162 | assert_eq!(iter.next(), None); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /tarantool/src/schema/function.rs: -------------------------------------------------------------------------------- 1 | //! Box schema: function. 2 | //! 3 | //! Helpers to create and modify functions in tarantool. 4 | //! 5 | //! Picodata fork of tarantool has an extended range of function identifiers. 6 | //! The first 32_000 identifiers (the maximum possible value in vanilla tarantool) 7 | //! are reserved for local functions. The rest of the identifiers (up to i32::MAX) 8 | //! are used for SQL procedures in picodata. 9 | 10 | use crate::error::{Error, TarantoolError}; 11 | use crate::ffi::tarantool::box_generate_func_id; 12 | 13 | fn next_id(use_reserved_range: bool) -> Result { 14 | unsafe { 15 | let mut id: u32 = 0; 16 | let res = box_generate_func_id(&mut id, use_reserved_range); 17 | if res != 0 { 18 | return Err(TarantoolError::last().into()); 19 | } 20 | Ok(id) 21 | } 22 | } 23 | 24 | /// Generate next function id from reserved range 25 | /// (used for stored procedures in picodata). 26 | pub fn func_next_reserved_id() -> Result { 27 | next_id(true) 28 | } 29 | 30 | /// Generate next function id from default range 31 | /// (used for local tarantool functions). 32 | pub fn func_next_id() -> Result { 33 | next_id(false) 34 | } 35 | 36 | #[cfg(feature = "internal_test")] 37 | mod tests { 38 | use super::*; 39 | use std::convert::TryInto; 40 | 41 | #[crate::test(tarantool = "crate")] 42 | pub fn test_func_next_id() { 43 | let id = func_next_id().unwrap(); 44 | assert!(id > 0 && id <= 32_000); 45 | } 46 | 47 | #[crate::test(tarantool = "crate")] 48 | pub fn test_func_next_reserved_id() { 49 | let id = func_next_reserved_id().unwrap(); 50 | assert!(id > 32_000 && id <= i32::MAX.try_into().unwrap()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tarantool/src/schema/index.rs: -------------------------------------------------------------------------------- 1 | use crate::c_ptr; 2 | use crate::error::{Error, TarantoolError}; 3 | use crate::ffi::lua; 4 | use crate::ffi::tarantool::luaT_call; 5 | use crate::index::{Index, IndexOptions}; 6 | use tlua::AsLua as _; 7 | use tlua::{ 8 | LuaError::{self, ExecutionError}, 9 | LuaFunction, LuaTable, 10 | }; 11 | 12 | /// Create new index for space. 13 | /// 14 | /// - `space_id` - ID of existing space. 15 | /// - `index_name` - name of index to create, which should conform to the rules for object names. 16 | /// - `opts` - see IndexOptions struct. 17 | /// 18 | /// For details see [space_object:create_index](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/create_index/) 19 | pub fn create_index(space_id: u32, index_name: &str, opts: &IndexOptions) -> Result { 20 | let lua = crate::lua_state(); 21 | let b: LuaTable<_> = lua 22 | .get("box") 23 | .ok_or_else(|| ExecutionError("box == nil".into()))?; 24 | let b_schema: LuaTable<_> = b 25 | .get("schema") 26 | .ok_or_else(|| ExecutionError("box.schema == nil".into()))?; 27 | let b_s_index: LuaTable<_> = b_schema 28 | .get("index") 29 | .ok_or_else(|| ExecutionError("box.schema.index == nil".into()))?; 30 | let index_create: LuaFunction<_> = b_s_index 31 | .get("create") 32 | .ok_or_else(|| ExecutionError("box.schema.index.create == nil".into()))?; 33 | let new_index: LuaTable<_> = index_create 34 | .call_with_args((space_id, index_name, opts)) 35 | .map_err(LuaError::from)?; 36 | let index_id: u32 = new_index.get("id").ok_or_else(|| { 37 | ExecutionError(format!("box.space[{}].index['{}'] == nil", space_id, index_name).into()) 38 | })?; 39 | Ok(Index::new(space_id, index_id)) 40 | } 41 | 42 | /// Drop existing index. 43 | /// 44 | /// - `space_id` - ID of existing space. 45 | /// - `index_name` - ID of existing index. 46 | pub fn drop_index(space_id: u32, index_id: u32) -> Result<(), Error> { 47 | unsafe { 48 | // Create new stack (just in case - in order no to mess things 49 | // in current stack). 50 | let lua = crate::lua_state(); 51 | let drop_state = lua.as_lua(); 52 | 53 | // Execute the following Lua code: 54 | // -- space = box.space._space:get(space_id) 55 | // -- space_name = space.name 56 | // -- index = box.space._index:get({space_id, index_id}) 57 | // -- index_name = index.name 58 | // -- box.space.space_name.index.index_name:drop() 59 | 60 | // -- space = box.space._space:get({"id": space_id}) 61 | lua::lua_getglobal(drop_state, c_ptr!("box")); 62 | lua::lua_getfield(drop_state, -1, c_ptr!("space")); 63 | lua::lua_getfield(drop_state, -1, c_ptr!("_space")); 64 | lua::lua_getfield(drop_state, -1, c_ptr!("get")); 65 | lua::lua_pushvalue(drop_state, -2); 66 | lua::lua_pushinteger(drop_state, space_id as isize); 67 | if luaT_call(drop_state, 2, 1) == 1 { 68 | return Err(TarantoolError::last().into()); 69 | } 70 | 71 | // -- space_name = space.name 72 | lua::lua_getfield(drop_state, -1, c_ptr!("name")); 73 | let space_name = lua::lua_tostring(drop_state, -1); 74 | lua::lua_remove(drop_state, -1); 75 | 76 | // -- index = box.space._index:get({space_id, index_id}) 77 | lua::lua_getglobal(drop_state, c_ptr!("box")); 78 | lua::lua_getfield(drop_state, -1, c_ptr!("space")); 79 | lua::lua_getfield(drop_state, -1, c_ptr!("_index")); 80 | lua::lua_getfield(drop_state, -1, c_ptr!("get")); 81 | lua::lua_pushvalue(drop_state, -2); 82 | lua::lua_newtable(drop_state); 83 | lua::lua_pushinteger(drop_state, 1); 84 | lua::lua_pushinteger(drop_state, space_id as isize); 85 | lua::lua_settable(drop_state, -3); 86 | lua::lua_pushinteger(drop_state, 2); 87 | lua::lua_pushinteger(drop_state, index_id as isize); 88 | lua::lua_settable(drop_state, -3); 89 | if luaT_call(drop_state, 2, 1) == 1 { 90 | return Err(TarantoolError::last().into()); 91 | } 92 | 93 | // -- index_name = index.name 94 | lua::lua_getfield(drop_state, -1, c_ptr!("name")); 95 | let index_name = lua::lua_tostring(drop_state, -1); 96 | lua::lua_remove(drop_state, -1); 97 | 98 | // -- box.space.space_name.index.index_name:drop() 99 | lua::lua_getglobal(drop_state, c_ptr!("box")); 100 | lua::lua_getfield(drop_state, -1, c_ptr!("space")); 101 | lua::lua_getfield(drop_state, -1, space_name); 102 | lua::lua_getfield(drop_state, -1, c_ptr!("index")); 103 | lua::lua_getfield(drop_state, -1, index_name); 104 | lua::lua_getfield(drop_state, -1, c_ptr!("drop")); 105 | lua::lua_pushvalue(drop_state, -2); 106 | if luaT_call(drop_state, 1, 1) == 1 { 107 | return Err(TarantoolError::last().into()); 108 | } 109 | 110 | // No need to clean drop_state. It will be gc'ed. 111 | } 112 | 113 | Ok(()) 114 | } 115 | -------------------------------------------------------------------------------- /tarantool/src/schema/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "picodata")] 2 | pub mod function; 3 | pub mod index; 4 | pub mod sequence; 5 | pub mod space; 6 | 7 | use crate::error::Error; 8 | use crate::index::IteratorType; 9 | use crate::space::{Space, SystemSpace}; 10 | use crate::tuple::Tuple; 11 | 12 | fn resolve_user_or_role(user: &str) -> Result, Error> { 13 | let space_vuser: Space = SystemSpace::VUser.into(); 14 | let name_idx = space_vuser.index("name").unwrap(); 15 | Ok(match name_idx.get(&(user,))? { 16 | None => None, 17 | Some(user_tuple) => Some(user_tuple.field::(0)?.unwrap()), 18 | }) 19 | } 20 | 21 | /// Revoke all privileges associated with the given object. 22 | /// 23 | /// - `obj_type` - string representation of object's type. Can be one of the following: "space", "sequence" or "function". 24 | /// - `obj_id` - object's ID 25 | fn revoke_object_privileges(obj_type: &str, obj_id: u32) -> Result<(), Error> { 26 | let sys_vpriv: Space = SystemSpace::VPriv.into(); 27 | let sys_priv: Space = SystemSpace::Priv.into(); 28 | 29 | let index_obj = sys_vpriv.index("object").unwrap(); 30 | let privs: Vec = index_obj 31 | .select(IteratorType::Eq, &(obj_type, obj_id))? 32 | .collect(); 33 | 34 | for t in privs { 35 | let uid = t.field::(1)?.unwrap(); 36 | sys_priv.delete(&(uid,))?; 37 | } 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /tarantool/src/schema/sequence.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::schema; 3 | use crate::space::{Space, SystemSpace}; 4 | 5 | /// Drop existing sequence. 6 | /// 7 | /// - `seq_id` - ID of existing space. 8 | pub fn drop_sequence(seq_id: u32) -> Result<(), Error> { 9 | schema::revoke_object_privileges("sequence", seq_id)?; 10 | 11 | let sys_sequence: Space = SystemSpace::Sequence.into(); 12 | sys_sequence.delete(&(seq_id,))?; 13 | 14 | let sys_sequence_data: Space = SystemSpace::SequenceData.into(); 15 | sys_sequence_data.delete(&(seq_id,))?; 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /tarantool/src/sequence.rs: -------------------------------------------------------------------------------- 1 | //! Box: sequences 2 | use crate::error::{Error, TarantoolError}; 3 | use crate::ffi::tarantool as ffi; 4 | use crate::space::{Space, SystemSpace}; 5 | 6 | /// A sequence is a generator of ordered integer values. 7 | pub struct Sequence { 8 | seq_id: u32, 9 | } 10 | 11 | impl Sequence { 12 | /// Find sequence by name. 13 | pub fn find(name: &str) -> Result, Error> { 14 | let space: Space = SystemSpace::Sequence.into(); 15 | let name_idx = space.index("name").unwrap(); 16 | 17 | Ok(match name_idx.get(&(name,))? { 18 | None => None, 19 | Some(row_tuple) => Some(Sequence { 20 | seq_id: row_tuple.field(0)?.unwrap(), 21 | }), 22 | }) 23 | } 24 | 25 | #[allow(clippy::should_implement_trait)] 26 | /// Generate the next value and return it. 27 | /// 28 | /// The generation algorithm is simple: 29 | /// - If this is the first time, then return the `start` value. 30 | /// - If the previous value plus the `increment` value is less than the `minimum` value or greater than the 31 | /// `maximum` value, that is "overflow", so either raise an error (if `cycle = false`) or return the `maximum` value 32 | /// (if `cycle = true` and `step < 0`) or return the `minimum` value (if `cycle = true` and `step > 0`). 33 | /// 34 | /// If there was no error, then save the returned result, it is now the "previous value". 35 | pub fn next(&mut self) -> Result { 36 | let mut result: i64 = 0; 37 | if unsafe { ffi::box_sequence_next(self.seq_id, &mut result) } < 0 { 38 | Err(TarantoolError::last().into()) 39 | } else { 40 | Ok(result) 41 | } 42 | } 43 | 44 | /// Set the "previous value" to `new_value`. 45 | /// 46 | /// This function requires a "write" privilege on the sequence. 47 | pub fn set(&mut self, new_value: i64) -> Result<(), Error> { 48 | if unsafe { ffi::box_sequence_set(self.seq_id, new_value) } < 0 { 49 | Err(TarantoolError::last().into()) 50 | } else { 51 | Ok(()) 52 | } 53 | } 54 | 55 | /// Set the sequence back to its original state. 56 | /// 57 | /// The effect is that a subsequent [next](#method.next) will return the start value. 58 | /// This function requires a "write" privilege on the sequence. 59 | pub fn reset(&mut self) -> Result<(), Error> { 60 | if unsafe { ffi::box_sequence_reset(self.seq_id) } < 0 { 61 | Err(TarantoolError::last().into()) 62 | } else { 63 | Ok(()) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tarantool/src/session.rs: -------------------------------------------------------------------------------- 1 | //! Box: session 2 | //! 3 | //! A session is an object associated with each client connection. 4 | //! box.session submodule provides functions to query session state. 5 | //! 6 | //! See also: 7 | //! - [Lua reference: Submodule box.session](https://www.tarantool.io/en/doc/1.10/reference/reference_lua/box_session/) 8 | 9 | pub type UserId = u32; 10 | 11 | #[cfg(not(feature = "picodata"))] 12 | mod vanilla { 13 | use std::convert::TryFrom; 14 | use std::ffi::CString; 15 | use tlua::{AsLua as _, LuaError}; 16 | 17 | use crate::error::{Error, TarantoolError}; 18 | use crate::ffi::lua as ffi_lua; 19 | use crate::ffi::tarantool::luaT_call; 20 | 21 | use super::UserId; 22 | 23 | fn user_id_from_lua(id: isize) -> UserId { 24 | // id in box.space._user has type unsigned 25 | // user_by_id in user.h uses uint32_t as parameter type 26 | u32::try_from(id).expect("user id is always valid u32") 27 | } 28 | 29 | /// Get the user ID from the current session. 30 | pub fn uid() -> Result { 31 | unsafe { 32 | // Create new stack (just in case - in order no to mess things 33 | // in current stack). 34 | let lua = crate::lua_state(); 35 | let uid_state = lua.as_lua(); 36 | 37 | // Push box.session.uid function on the stack. 38 | let name_box = CString::new("box").unwrap(); 39 | ffi_lua::lua_getglobal(uid_state, name_box.as_ptr()); 40 | let name_session = CString::new("session").unwrap(); 41 | ffi_lua::lua_getfield(uid_state, -1, name_session.as_ptr()); 42 | let name_uid = CString::new("uid").unwrap(); 43 | ffi_lua::lua_getfield(uid_state, -1, name_uid.as_ptr()); 44 | 45 | if luaT_call(uid_state, 0, 1) == 1 { 46 | Err(TarantoolError::last().into()) 47 | } else { 48 | Ok(user_id_from_lua(ffi_lua::lua_tointeger(uid_state, -1))) 49 | } 50 | 51 | // No need to clean uid_state. It will be gc'ed. 52 | } 53 | } 54 | 55 | /// Get the effective user ID of the current session. 56 | pub fn euid() -> Result { 57 | unsafe { 58 | // Create new stack (just in case - in order no to mess things 59 | // in current stack). 60 | let lua = crate::lua_state(); 61 | let euid_state = lua.as_lua(); 62 | 63 | // Push box.session.euid on the stack. 64 | let name = CString::new("box").unwrap(); 65 | ffi_lua::lua_getglobal(euid_state, name.as_ptr()); 66 | let name_session = CString::new("session").unwrap(); 67 | ffi_lua::lua_getfield(euid_state, -1, name_session.as_ptr()); 68 | let name_euid = CString::new("euid").unwrap(); 69 | ffi_lua::lua_getfield(euid_state, -1, name_euid.as_ptr()); 70 | 71 | if luaT_call(euid_state, 0, 1) == 1 { 72 | Err(TarantoolError::last().into()) 73 | } else { 74 | Ok(user_id_from_lua(ffi_lua::lua_tointeger(euid_state, -1))) 75 | } 76 | 77 | // No need to clean euid_state. It will be gc'ed. 78 | } 79 | } 80 | 81 | pub(super) fn su_impl(uid: UserId) -> Result<(), Error> { 82 | let lua = crate::lua_state(); 83 | lua.exec_with("box.session.su(...)", uid) 84 | .map_err(LuaError::from)?; 85 | 86 | Ok(()) 87 | } 88 | } 89 | 90 | #[cfg(feature = "picodata")] 91 | mod picodata { 92 | use crate::{ 93 | error::{Error, TarantoolError}, 94 | ffi::tarantool::{ 95 | box_effective_user_id, box_session_su, box_session_user_id, box_user_id_by_name, 96 | }, 97 | }; 98 | 99 | use super::UserId; 100 | 101 | /// Get the user ID of the current user. 102 | #[inline] 103 | pub fn uid() -> Result { 104 | unsafe { 105 | let mut ret: u32 = 0; 106 | let err = box_session_user_id(&mut ret); 107 | if err < 0 { 108 | return Err(Error::Tarantool(TarantoolError::last())); 109 | } 110 | Ok(ret) 111 | } 112 | } 113 | 114 | /// Get the effective user ID of the current user. 115 | #[inline] 116 | pub fn euid() -> Result { 117 | // In picodata this is actually infallible. 118 | unsafe { Ok(box_effective_user_id()) } 119 | } 120 | 121 | pub(super) fn su_impl(uid: UserId) -> Result<(), Error> { 122 | let err = unsafe { box_session_su(uid) }; 123 | if err < 0 { 124 | return Err(Error::Tarantool(TarantoolError::last())); 125 | } 126 | Ok(()) 127 | } 128 | 129 | #[inline] 130 | pub fn user_id_by_name(name: &str) -> Result { 131 | let name_range = name.as_bytes().as_ptr_range(); 132 | let mut uid: u32 = 0; 133 | let err = unsafe { 134 | box_user_id_by_name(name_range.start.cast(), name_range.end.cast(), &mut uid) 135 | }; 136 | if err < 0 { 137 | return Err(Error::Tarantool(TarantoolError::last())); 138 | } 139 | Ok(uid) 140 | } 141 | } 142 | 143 | use crate::error::Error; 144 | 145 | #[cfg(feature = "picodata")] 146 | pub use picodata::*; 147 | 148 | #[cfg(not(feature = "picodata"))] 149 | pub use vanilla::*; 150 | 151 | pub struct SuGuard { 152 | pub original_user_id: UserId, 153 | } 154 | 155 | impl Drop for SuGuard { 156 | fn drop(&mut self) { 157 | su_impl(self.original_user_id).expect("failed to switch back to original user"); 158 | } 159 | } 160 | 161 | #[inline] 162 | pub fn su(target_uid: UserId) -> Result { 163 | let original_user_id = uid().expect("infallible with c api"); 164 | su_impl(target_uid)?; 165 | 166 | Ok(SuGuard { original_user_id }) 167 | } 168 | 169 | #[inline] 170 | pub fn with_su(uid: UserId, f: impl FnOnce() -> T) -> Result { 171 | let _su = su(uid)?; 172 | Ok(f()) 173 | } 174 | -------------------------------------------------------------------------------- /tarantool/src/sql.rs: -------------------------------------------------------------------------------- 1 | #![cfg(any(feature = "picodata", doc))] 2 | 3 | use crate::error::TarantoolError; 4 | use crate::ffi; 5 | use crate::ffi::sql::{ObufWrapper, PortC}; 6 | use serde::Serialize; 7 | use std::borrow::Cow; 8 | use std::io::Read; 9 | use std::os::raw::c_char; 10 | use std::str; 11 | 12 | const MP_EMPTY_ARRAY: &[u8] = &[0x90]; 13 | 14 | /// Returns the hash, used as the statement ID, generated from the SQL query text. 15 | pub fn calculate_hash(sql: &str) -> u32 { 16 | unsafe { ffi::sql::sql_stmt_calculate_id(sql.as_ptr() as *const c_char, sql.len()) } 17 | } 18 | 19 | /// Executes an SQL query without storing the prepared statement in the instance 20 | /// cache and returns a wrapper around the raw msgpack bytes. 21 | pub fn prepare_and_execute_raw( 22 | query: &str, 23 | bind_params: &IN, 24 | vdbe_max_steps: u64, 25 | ) -> crate::Result 26 | where 27 | IN: Serialize, 28 | { 29 | let mut buf = ObufWrapper::new(1024); 30 | let mut param_data = Cow::from(MP_EMPTY_ARRAY); 31 | if std::mem::size_of::() != 0 { 32 | param_data = Cow::from(rmp_serde::to_vec(bind_params)?); 33 | debug_assert!(crate::msgpack::skip_value(&mut std::io::Cursor::new(¶m_data)).is_ok()); 34 | } 35 | let param_ptr = param_data.as_ptr() as *const u8; 36 | let execute_result = unsafe { 37 | ffi::sql::sql_prepare_and_execute_ext( 38 | query.as_ptr() as *const u8, 39 | query.len() as i32, 40 | param_ptr, 41 | vdbe_max_steps, 42 | buf.obuf(), 43 | ) 44 | }; 45 | if execute_result < 0 { 46 | return Err(TarantoolError::last().into()); 47 | } 48 | Ok(buf) 49 | } 50 | 51 | pub fn sql_execute_into_port( 52 | query: &str, 53 | bind_params: &IN, 54 | vdbe_max_steps: u64, 55 | port: &mut PortC, 56 | ) -> crate::Result<()> 57 | where 58 | IN: Serialize, 59 | { 60 | let mut param_data = Cow::from(MP_EMPTY_ARRAY); 61 | if std::mem::size_of::() != 0 { 62 | param_data = Cow::from(rmp_serde::to_vec(bind_params)?); 63 | debug_assert!(crate::msgpack::skip_value(&mut std::io::Cursor::new(¶m_data)).is_ok()); 64 | } 65 | let param_ptr = param_data.as_ptr() as *const u8; 66 | let execute_result = unsafe { 67 | ffi::sql::sql_execute_into_port( 68 | query.as_ptr() as *const u8, 69 | query.len() as i32, 70 | param_ptr, 71 | vdbe_max_steps, 72 | port.as_mut_ptr(), 73 | ) 74 | }; 75 | if execute_result < 0 { 76 | return Err(TarantoolError::last().into()); 77 | } 78 | Ok(()) 79 | } 80 | 81 | /// Creates new SQL prepared statement and stores it in the session. 82 | /// query - SQL query. 83 | /// 84 | /// Keep in mind that a prepared statement is stored in the instance cache as 85 | /// long as its reference counter is non-zero. The counter increases only when 86 | /// a new statement is added to a session. Repeatedly calling prepare on an 87 | /// already existing statement within the same session does not increase the 88 | /// instance cache counter. However, calling prepare on the statement in a 89 | /// different session without the statement does increase the counter. 90 | pub fn prepare(query: String) -> crate::Result { 91 | let mut stmt_id: u32 = 0; 92 | let mut session_id: u64 = 0; 93 | 94 | if unsafe { 95 | ffi::sql::sql_prepare_ext( 96 | query.as_ptr(), 97 | query.len() as u32, 98 | &mut stmt_id as *mut u32, 99 | &mut session_id as *mut u64, 100 | ) 101 | } < 0 102 | { 103 | return Err(TarantoolError::last().into()); 104 | } 105 | 106 | Ok(Statement { 107 | query, 108 | stmt_id, 109 | session_id, 110 | }) 111 | } 112 | 113 | /// Removes SQL prepared statement from the session. 114 | /// 115 | /// The statement is removed from the session, and its reference counter in 116 | /// the instance cache is decremented. If the counter reaches zero, the 117 | /// statement is removed from the instance cache. 118 | pub fn unprepare(stmt: Statement) -> crate::Result<()> { 119 | if unsafe { ffi::sql::sql_unprepare_ext(stmt.id(), stmt.session_id()) } < 0 { 120 | return Err(TarantoolError::last().into()); 121 | } 122 | Ok(()) 123 | } 124 | 125 | /// SQL prepared statement. 126 | #[derive(Default, Debug)] 127 | pub struct Statement { 128 | query: String, 129 | stmt_id: u32, 130 | session_id: u64, 131 | } 132 | 133 | impl Statement { 134 | /// Returns original query. 135 | pub fn source(&self) -> &str { 136 | self.query.as_str() 137 | } 138 | 139 | /// Returns the statement ID generated from the SQL query text. 140 | pub fn id(&self) -> u32 { 141 | self.stmt_id 142 | } 143 | 144 | /// Returns the session ID. 145 | pub fn session_id(&self) -> u64 { 146 | self.session_id 147 | } 148 | 149 | /// Executes prepared statement and returns a wrapper over the raw msgpack bytes. 150 | pub fn execute_raw(&self, bind_params: &IN, vdbe_max_steps: u64) -> crate::Result 151 | where 152 | IN: Serialize, 153 | { 154 | let mut buf = ObufWrapper::new(1024); 155 | let mut param_data = Cow::from(MP_EMPTY_ARRAY); 156 | if std::mem::size_of::() != 0 { 157 | param_data = Cow::from(rmp_serde::to_vec(bind_params)?); 158 | debug_assert!( 159 | crate::msgpack::skip_value(&mut std::io::Cursor::new(¶m_data)).is_ok() 160 | ); 161 | } 162 | let param_ptr = param_data.as_ptr() as *const u8; 163 | let execute_result = unsafe { 164 | ffi::sql::sql_execute_prepared_ext(self.id(), param_ptr, vdbe_max_steps, buf.obuf()) 165 | }; 166 | 167 | if execute_result < 0 { 168 | return Err(TarantoolError::last().into()); 169 | } 170 | Ok(buf) 171 | } 172 | 173 | pub fn execute_into_port( 174 | &self, 175 | bind_params: &IN, 176 | vdbe_max_steps: u64, 177 | port: &mut PortC, 178 | ) -> crate::Result<()> 179 | where 180 | IN: Serialize, 181 | { 182 | let mut param_data = Cow::from(MP_EMPTY_ARRAY); 183 | if std::mem::size_of::() != 0 { 184 | param_data = Cow::from(rmp_serde::to_vec(bind_params)?); 185 | debug_assert!( 186 | crate::msgpack::skip_value(&mut std::io::Cursor::new(¶m_data)).is_ok() 187 | ); 188 | } 189 | let param_ptr = param_data.as_ptr() as *const u8; 190 | let execute_result = unsafe { 191 | ffi::sql::stmt_execute_into_port( 192 | self.id(), 193 | param_ptr, 194 | vdbe_max_steps, 195 | port.as_mut_ptr(), 196 | ) 197 | }; 198 | 199 | if execute_result < 0 { 200 | return Err(TarantoolError::last().into()); 201 | } 202 | Ok(()) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /tarantool/src/transaction.rs: -------------------------------------------------------------------------------- 1 | //! Transaction management 2 | //! 3 | //! For general information and examples, see 4 | //! [Transaction control](https://www.tarantool.io/en/doc/latest/book/box/atomic_index/#atomic-atomic-execution). 5 | //! 6 | //! Observe the following rules when working with transactions: 7 | //! 8 | //! 👉 **Rule #1** 9 | //! The requests in a transaction must be sent to a server as a single block. 10 | //! It is not enough to enclose them between begin and commit or rollback. 11 | //! To ensure they are sent as a single block: put them in a function, or put them all on one line, or use a delimiter 12 | //! so that multi-line requests are handled together. 13 | //! 14 | //! 👉 **Rule #2** 15 | //! All database operations in a transaction should use the same storage engine. 16 | //! It is not safe to access tuple sets that are defined with `{engine='vinyl'}` and also access tuple sets that are 17 | //! defined with `{engine='memtx'}`, in the same transaction. 18 | //! 19 | //! 👉 **Rule #3** 20 | //! Requests which cause changes to the data definition – create, alter, drop, truncate – are only allowed with 21 | //! Tarantool version 2.1 or later. Data-definition requests which change an index or change a format, such as 22 | //! `space_object:create_index()` and `space_object:format()`, are not allowed inside transactions except as the first 23 | //! request. 24 | //! 25 | //! See also: 26 | //! - [Transaction control](https://www.tarantool.io/en/doc/latest/book/box/atomic/) 27 | //! - [Lua reference: Functions for transaction management](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_txn_management/) 28 | //! - [C API reference: Module txn](https://www.tarantool.io/en/doc/latest/dev_guide/reference_capi/txn/) 29 | 30 | use crate::error::TarantoolError; 31 | use crate::ffi::tarantool as ffi; 32 | 33 | /// Transaction-related error cases 34 | #[derive(Debug, thiserror::Error)] 35 | pub enum TransactionError { 36 | #[error("transaction has already been started")] 37 | AlreadyStarted, 38 | 39 | #[error("failed to commit: {0}")] 40 | FailedToCommit(TarantoolError), 41 | 42 | #[error("failed to rollback: {0}")] 43 | FailedToRollback(TarantoolError), 44 | 45 | #[error("transaction rolled-back: {0}")] 46 | RolledBack(E), 47 | } 48 | 49 | /// Executes a transaction in the current fiber. 50 | /// 51 | /// A transaction is attached to caller fiber, therefore one fiber can have 52 | /// only one active transaction. 53 | /// 54 | /// - `f` - function will be invoked within transaction 55 | /// 56 | /// Returns result of function `f` execution. Depending on the function result: 57 | /// - will **commit** - if function completes successfully 58 | /// - will **rollback** - if function completes with any error 59 | pub fn transaction(f: F) -> Result> 60 | where 61 | F: FnOnce() -> Result, 62 | { 63 | if unsafe { ffi::box_txn_begin() } < 0 { 64 | return Err(TransactionError::AlreadyStarted); 65 | } 66 | 67 | let result = f(); 68 | match &result { 69 | Ok(_) => { 70 | if unsafe { ffi::box_txn_commit() } < 0 { 71 | let error = TarantoolError::last(); 72 | return Err(TransactionError::FailedToCommit(error)); 73 | } 74 | } 75 | Err(_) => { 76 | if unsafe { ffi::box_txn_rollback() } < 0 { 77 | let error = TarantoolError::last(); 78 | return Err(TransactionError::FailedToRollback(error)); 79 | } 80 | } 81 | } 82 | result.map_err(TransactionError::RolledBack) 83 | } 84 | 85 | /// Returns `true` if there's an active transaction. 86 | #[inline(always)] 87 | pub fn is_in_transaction() -> bool { 88 | unsafe { ffi::box_txn() } 89 | } 90 | 91 | /// Begin a transaction in the current fiber. 92 | /// 93 | /// One fiber can have at most one active transaction. 94 | /// 95 | /// Returns an error if there's already an active transcation. 96 | /// May return an error in other cases. 97 | /// 98 | /// **NOTE:** it is the caller's responsibility to call [`commit`] or 99 | /// [`rollback`]. Consider using [`transaction`] instead. 100 | #[inline(always)] 101 | pub fn begin() -> Result<(), TarantoolError> { 102 | if unsafe { ffi::box_txn_begin() } < 0 { 103 | return Err(TarantoolError::last()); 104 | } 105 | Ok(()) 106 | } 107 | 108 | /// Commit the active transaction. 109 | /// 110 | /// Returns `Ok(())` if there is no active transaction. 111 | /// 112 | /// Returns an error in case of IO failure. 113 | /// May return an error in other cases. 114 | #[inline(always)] 115 | pub fn commit() -> Result<(), TarantoolError> { 116 | if unsafe { ffi::box_txn_commit() } < 0 { 117 | return Err(TarantoolError::last()); 118 | } 119 | Ok(()) 120 | } 121 | 122 | /// Rollback the active transaction. 123 | /// 124 | /// Returns `Ok(())` if there is no active transaction. 125 | /// 126 | /// Returns an error if called from a nested statement, e.g. when called via a trigger. 127 | /// May return an error in other cases. 128 | #[inline(always)] 129 | pub fn rollback() -> Result<(), TarantoolError> { 130 | if unsafe { ffi::box_txn_rollback() } < 0 { 131 | return Err(TarantoolError::last()); 132 | } 133 | Ok(()) 134 | } 135 | -------------------------------------------------------------------------------- /tarantool/src/trigger.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{TarantoolError, TarantoolErrorCode}; 2 | use crate::ffi::tarantool as ffi; 3 | use crate::set_error; 4 | use std::io; 5 | 6 | /// Set a callback to be called on Tarantool shutdown. 7 | pub fn on_shutdown(cb: F) -> Result<(), TarantoolError> { 8 | let cb_ptr = Box::into_raw(Box::new(cb)); 9 | if unsafe { ffi::box_on_shutdown(cb_ptr as _, Some(trampoline::), None) } != 0 { 10 | if io::Error::last_os_error().kind() == io::ErrorKind::InvalidInput { 11 | set_error!( 12 | TarantoolErrorCode::IllegalParams, 13 | "invalid arguments to on_shutdown" 14 | ); 15 | } 16 | return Err(TarantoolError::last()); 17 | } 18 | 19 | return Ok(()); 20 | 21 | use libc::{c_int, c_void}; 22 | extern "C" fn trampoline(data: *mut c_void) -> c_int { 23 | let cb = unsafe { Box::from_raw(data as *mut F) }; 24 | cb(); 25 | 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tarantool-module-test-runner" 3 | version = "0.1.0" 4 | authors = [ 5 | "Dmitriy Koltsov ", 6 | "Georgy Moshkin ", 7 | "Anton Melnikov ", 8 | "Egor Ivkov ", 9 | ] 10 | edition = "2018" 11 | license = "BSD-2-Clause" 12 | rust-version = "1.82" 13 | 14 | [dependencies] 15 | log = "0.4.11" 16 | rand = "0.7.3" 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_json = "1.0" 19 | serde_plain = "1.0" 20 | serde_bytes = "*" 21 | tester = "0.7.0" 22 | once_cell = "1.9.0" 23 | rmp = "=0.8.11" 24 | rmp-serde = "1" 25 | rmpv = { version = "1", features = ["with-serde"] } 26 | libc = "*" 27 | futures = "0.3.25" 28 | linkme = "0.3" 29 | time = "0.3.37" # not used directly, but referenced by macro expansions from time-macros 30 | time-macros = "0.2.6" 31 | 32 | [dependencies.tarantool] 33 | path = "../tarantool" 34 | features = ["all", "internal_test", "stored_procs_slice"] 35 | 36 | [dependencies.tarantool-proc] 37 | path = "../tarantool-proc" 38 | features = ["stored_procs_slice"] 39 | 40 | [lib] 41 | test = false 42 | crate-type = ["cdylib"] 43 | 44 | [[test]] 45 | name = "run_tests" 46 | path = "run.rs" 47 | harness = false 48 | 49 | [features] 50 | picodata = ["tarantool/picodata"] 51 | standalone_decimal = ["tarantool/standalone_decimal"] 52 | tokio_components = ["tarantool/tokio_components"] 53 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## How to run 2 | With `make`: 3 | ```bash 4 | make test 5 | ``` 6 | 7 | With `cargo`: 8 | ```bash 9 | cargo build -p tarantool-module-test-runner 10 | cargo test 11 | ``` 12 | 13 | ## Under the hood 14 | When running `cargo test` some of the steps would be as you would expect: 15 | - Unit tests and integration tests defined with `#[test]` macro 16 | - Doc tests - code in documentation 17 | 18 | But also it would execute our custom test runner which main fn is defined in `tests/run.rs`. 19 | Then the steps would be the following: 20 | 1. Test runner starts `tarantool` with `run_tests.lua` script as an argument 21 | 2. `run_tests.lua` script does some initialization and loads `tarantool-module-test-runner` built as a dynamic library 22 | 3. Then `run_tests.lua` calls the main function of this module, which effectively is `start` in `tests/src/lib.rs` 23 | 4. `start` creates test spaces and calls `run_tests` 24 | 5. `run_tests` collects tests defined with `linkme::distributed_slice` macro from `tarantool` lib 25 | 6. `run_tests` collects tests that are explicitely added there by full path 26 | 7. `run_tests` feeds all tests to Rust default console test runner and collects result 27 | 8. result is reported through all the chain to the top and the process exits with appropriate exit code 28 | 29 | All this is done as integration tests expect to be run from inside a `tarantool` instance. 30 | 31 | ## How to add tests 32 | ### If you don't need tarantool environment 33 | Just add a test with `#[test]` macro. 34 | You shouldn't use any `tarantool` symbols or symbols dependent on them in this case. 35 | 36 | ### Otherwise 37 | #### Recommended 38 | Use `linkme::distributed_slice` macro to add tests. See `tarantool::test` module docs for examples. 39 | 40 | #### Deprecated 41 | Insert the test directly by path into `run_tests` fn in `tests/src/lib.rs` 42 | 43 | -------------------------------------------------------------------------------- /tests/run.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::env; 3 | use std::process::Command; 4 | 5 | #[derive(Serialize, Deserialize)] 6 | struct Metadata { 7 | workspace_root: String, 8 | } 9 | 10 | fn main() -> Result<(), i32> { 11 | let filter = env::args().skip(1); 12 | let tarantool_exec = 13 | env::var("TARANTOOL_EXECUTABLE").unwrap_or_else(|_| "tarantool".to_owned()); 14 | let metadata = Command::new("cargo") 15 | .arg("metadata") 16 | .arg("--format-version=1") 17 | .output() 18 | .expect("failed to get cargo metadata output"); 19 | let metadata: Metadata = 20 | serde_json::from_slice(&metadata.stdout).expect("failed to parse cargo metadata output"); 21 | let status = Command::new(tarantool_exec) 22 | .arg(format!("{}/tests/run_tests.lua", metadata.workspace_root)) 23 | .args(filter) 24 | .status() 25 | .expect("failed to run tarantool child process"); 26 | if status.success() { 27 | Ok(()) 28 | } else { 29 | Err(1) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/run_tests.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | json = require('json') 3 | 4 | local fio = require('fio') 5 | local fiber = require('fiber') 6 | local log = require('log') 7 | 8 | local tmpdir = fio.tempdir() 9 | 10 | -- Change current working directory into the one this file is in 11 | local current_file = debug.getinfo(1).source 12 | while current_file:sub(1, 1) == '@' do 13 | current_file = current_file:sub(2) 14 | end 15 | local current_dir = fio.dirname(current_file) 16 | fio.chdir(current_dir) 17 | 18 | box.cfg{ 19 | log_level = 'verbose', 20 | listen = 'localhost:0', 21 | wal_mode = 'none', 22 | memtx_dir = tmpdir, 23 | wal_dir = tmpdir, 24 | } 25 | 26 | log.info("version: " .. box.info.version) 27 | 28 | fio.rmtree(tmpdir) 29 | 30 | -- Init test database 31 | box.once('bootstrap_tests', function() 32 | box.schema.user.create('test_user', { password = 'password' }) 33 | box.schema.user.grant('test_user', 'read,write,execute,create,drop', 'universe') 34 | 35 | box.schema.sequence.create('test_seq') 36 | 37 | box.schema.func.create('test_stored_proc') 38 | box.schema.func.create('test_schema_update') 39 | box.schema.func.create('test_schema_cleanup') 40 | end) 41 | 42 | function test_stored_proc(a, b) 43 | return a + b 44 | end 45 | 46 | function test_timeout() 47 | fiber.sleep(1.5) 48 | end 49 | 50 | function test_schema_update() 51 | box.schema.space.create('test_s_tmp') 52 | end 53 | 54 | function test_schema_cleanup() 55 | box.space.test_s_tmp:drop() 56 | end 57 | 58 | function target_dir() 59 | if rawget(_G, '_target_dir') == nil then 60 | local data = io.popen('cargo metadata --format-version 1'):read('*l') 61 | rawset(_G, '_target_dir', json.decode(data).target_directory) 62 | end 63 | return _target_dir 64 | end 65 | 66 | function build_mode() 67 | local build_mode_env = os.getenv('TARANTOOL_MODULE_BUILD_MODE') 68 | if not build_mode_env then 69 | build_mode_env = 'debug' 70 | end 71 | return build_mode_env 72 | end 73 | 74 | -- Add test runner library location to lua search path 75 | package.cpath = string.format( 76 | '%s/%s/?.so;%s/%s/?.dylib;%s', 77 | target_dir(), build_mode(), 78 | target_dir(), build_mode(), 79 | package.cpath 80 | ) 81 | 82 | box.schema.func.create('libtarantool_module_test_runner.entry', { language = 'C' }) 83 | 84 | local cfg = { filter = arg[1] or "" } 85 | box.func['libtarantool_module_test_runner.entry']:call{cfg} 86 | 87 | os.exit(0) 88 | -------------------------------------------------------------------------------- /tests/src/auth.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "picodata")] 2 | 3 | use tarantool::auth::{AuthData, AuthMethod}; 4 | 5 | #[tarantool::test] 6 | pub fn md5() { 7 | let data = AuthData::new(&AuthMethod::Md5, "user", "password"); 8 | assert_eq!(&data.into_string(), "md54d45974e13472b5a0be3533de4666414"); 9 | } 10 | 11 | #[tarantool::test] 12 | pub fn chap_sha1() { 13 | let data = AuthData::new(&AuthMethod::ChapSha1, "", "password"); 14 | assert_eq!(&data.into_string(), "JHDAwG3uQv0WGLuZAFrcouydHhk="); 15 | } 16 | 17 | #[tarantool::test] 18 | pub fn ldap() { 19 | let data = AuthData::new(&AuthMethod::Ldap, "", ""); 20 | assert_eq!(&data.into_string(), ""); 21 | } 22 | -------------------------------------------------------------------------------- /tests/src/bench_bulk_insert.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | box.cfg{ 4 | listen = 3302, 5 | wal_mode = 'none' 6 | } 7 | 8 | ------------------------------------------------------------------------------ 9 | 10 | clock = require('clock') 11 | fiber = require('fiber') 12 | log = require('log') 13 | net_box = require('net.box') 14 | 15 | local test_size = 64; 16 | local num_fibers = 256; 17 | local num_rows = 1000; 18 | local num_passes = 30; 19 | 20 | local text = string.rep('X', test_size) 21 | local pass_times = {} 22 | 23 | ------------------------------------------------------------------------------ 24 | 25 | local conn = net_box.connect('bench_user:password@127.0.0.1:3301') 26 | conn:call('_cleanup') 27 | 28 | local id_base = 1 29 | for pass_id = 1, num_passes do 30 | local begin_time = tonumber(clock.monotonic64()) 31 | local local_space = box.space.bench_s1 32 | 33 | local fiber_pool = {} 34 | for fiber_id = 1, num_fibers do 35 | local fiber = fiber.create( 36 | function(id_base) 37 | local remote_space = conn.space.bench_s1 38 | for id = id_base, (id_base + num_rows - 1) do 39 | local res = pcall(remote_space.insert, remote_space, {id + id_base, text}) 40 | if not res then 41 | io.write('x') 42 | end 43 | end 44 | end, 45 | id_base 46 | ) 47 | fiber:set_joinable(true) 48 | table.insert(fiber_pool, fiber) 49 | id_base = id_base + num_rows 50 | end 51 | 52 | for i = 1, num_fibers do 53 | local fiber = fiber_pool[i] 54 | if fiber:status() ~= 'dead' then 55 | fiber:join() 56 | end 57 | end 58 | 59 | local end_time = tonumber(clock.monotonic64()) 60 | table.insert(pass_times, end_time - begin_time) 61 | io.write('.') 62 | io.flush() 63 | end 64 | 65 | conn:close() 66 | 67 | ------------------------------------------------------------------------------ 68 | 69 | local avg = 0 70 | for i = 1, num_passes do 71 | avg = avg + pass_times[i] 72 | end 73 | avg = avg / num_passes 74 | 75 | local std_div = 0 76 | for i = 1, num_passes do 77 | local t = pass_times[i] 78 | local dt = t - avg 79 | std_div = std_div + (dt * dt) 80 | end 81 | std_div = math.sqrt(std_div / num_passes) 82 | 83 | print(math.floor(avg)) 84 | print(math.floor(std_div)) 85 | 86 | os.exit(0) 87 | -------------------------------------------------------------------------------- /tests/src/bench_server.lua: -------------------------------------------------------------------------------- 1 | box.cfg{ 2 | listen = '127.0.0.1:3301', 3 | memtx_memory = 1024 * 1024 * 1024, 4 | net_msg_max = 500000, 5 | readahead = 1024 * 1024, 6 | wal_mode = 'none' 7 | } 8 | 9 | box.once('bootstrap_bench', function() 10 | box.schema.user.create('bench_user', { password = 'password' }) 11 | box.schema.user.grant('bench_user', 'read,write,execute,create,drop', 'universe') 12 | 13 | local bench_s1 = box.schema.space.create('bench_s1') 14 | bench_s1:format{ 15 | {name = 'id', type = 'unsigned'}, 16 | {name = 'text', type = 'string' } 17 | } 18 | bench_s1:create_index('primary', {type = 'TREE', parts = {{ field = 1, type = 'unsigned' }}}) 19 | 20 | box.schema.func.create('_cleanup') 21 | end) 22 | 23 | function _cleanup() 24 | box.space.bench_s1:truncate() 25 | end 26 | -------------------------------------------------------------------------------- /tests/src/coio.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use std::io::{Read, Write}; 3 | use std::net::{TcpListener, TcpStream}; 4 | use std::os::fd::OwnedFd; 5 | use std::os::unix::net::UnixStream; 6 | use std::time::Duration; 7 | 8 | use tarantool::coio::{self, channel, CoIOListener, CoIOStream}; 9 | use tarantool::fiber; 10 | 11 | pub fn coio_accept() { 12 | let tcp_listener = TcpListener::bind("127.0.0.1:0").unwrap(); 13 | let addr = tcp_listener.local_addr().unwrap(); 14 | 15 | let coio_listener: CoIOListener = tcp_listener.try_into().unwrap(); 16 | let client_fiber = fiber::start(|| { 17 | fiber::sleep(Duration::from_millis(10)); 18 | TcpStream::connect(addr).unwrap(); 19 | }); 20 | let accept_result = coio_listener.accept(); 21 | assert!(accept_result.is_ok()); 22 | client_fiber.join(); 23 | } 24 | 25 | pub fn coio_read_write() { 26 | let (reader_soc, writer_soc) = UnixStream::pair().unwrap(); 27 | 28 | let reader_fiber = fiber::start(move || { 29 | let mut stream = CoIOStream::new(TcpStream::from(OwnedFd::from(reader_soc))).unwrap(); 30 | let mut buf: Vec = vec![0; 4]; 31 | stream.read_exact(&mut buf).unwrap(); 32 | assert_eq!(buf, vec![1, 2, 3, 4]); 33 | }); 34 | 35 | let writer_fiber = fiber::start(move || { 36 | let mut stream = CoIOStream::new(TcpStream::from(OwnedFd::from(writer_soc))).unwrap(); 37 | stream.write_all(&[1, 2, 3, 4]).unwrap(); 38 | }); 39 | 40 | reader_fiber.join(); 41 | writer_fiber.join(); 42 | } 43 | 44 | pub fn coio_call() { 45 | let res = coio::coio_call( 46 | &mut |x| { 47 | assert_eq!(*x, 99); 48 | 100 49 | }, 50 | 99, 51 | ); 52 | assert_eq!(res, 100) 53 | } 54 | 55 | pub fn coio_channel() { 56 | let (tx, rx) = channel::(1); 57 | 58 | let fiber_a = fiber::start(move || { 59 | tx.send(99).unwrap(); 60 | }); 61 | 62 | let fiber_b = fiber::start(move || { 63 | let value = rx.recv().unwrap(); 64 | assert_eq!(value, 99); 65 | }); 66 | 67 | fiber_a.join(); 68 | fiber_b.join(); 69 | } 70 | 71 | pub fn channel_rx_closed() { 72 | let (tx, _) = channel::(1); 73 | 74 | let fiber = fiber::start(move || { 75 | assert!(tx.send(99).is_err()); 76 | }); 77 | fiber.join(); 78 | } 79 | 80 | pub fn channel_tx_closed() { 81 | let (_, rx) = channel::(1); 82 | 83 | let fiber = fiber::start(move || { 84 | assert!(rx.recv().is_none()); 85 | }); 86 | fiber.join(); 87 | } 88 | -------------------------------------------------------------------------------- /tests/src/common.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub use tarantool::test::util::LuaStackIntegrityGuard; 4 | use tarantool::tlua; 5 | use tarantool::tuple::Encode; 6 | 7 | #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] 8 | pub struct S1Record { 9 | pub id: u32, 10 | pub text: String, 11 | } 12 | 13 | impl Encode for S1Record {} 14 | 15 | #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] 16 | pub struct S2Record { 17 | pub id: u32, 18 | pub key: String, 19 | pub value: String, 20 | pub a: i32, 21 | pub b: i32, 22 | } 23 | 24 | impl Encode for S2Record {} 25 | 26 | #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] 27 | pub struct S2Key { 28 | pub id: u32, 29 | pub a: i32, 30 | pub b: i32, 31 | } 32 | 33 | impl Encode for S2Key {} 34 | 35 | #[derive(Serialize)] 36 | pub struct QueryOperation { 37 | pub op: String, 38 | pub field_id: u32, 39 | pub value: serde_json::Value, 40 | } 41 | 42 | impl Encode for QueryOperation {} 43 | 44 | #[derive(Clone, Debug)] 45 | pub(crate) struct DropCounter(pub(crate) std::rc::Rc>); 46 | 47 | impl Drop for DropCounter { 48 | fn drop(&mut self) { 49 | let old_count = self.0.get(); 50 | self.0.set(old_count + 1); 51 | } 52 | } 53 | 54 | pub(crate) fn capture_value(_: &T) {} 55 | 56 | pub(crate) struct LuaContextSpoiler { 57 | fix: Option>, 58 | } 59 | 60 | impl LuaContextSpoiler { 61 | pub fn new(spoil: &str, fix: &'static str) -> Self { 62 | tarantool::lua_state().exec(spoil).unwrap(); 63 | Self { 64 | fix: Some(Box::new(move || global_lua().exec(fix).unwrap())), 65 | } 66 | } 67 | } 68 | 69 | impl Drop for LuaContextSpoiler { 70 | fn drop(&mut self) { 71 | (self.fix.take().unwrap())() 72 | } 73 | } 74 | 75 | fn global_lua() -> tlua::StaticLua { 76 | unsafe { tlua::Lua::from_static(tarantool::ffi::tarantool::luaT_state()) } 77 | } 78 | 79 | use once_cell::unsync::OnceCell; 80 | 81 | pub fn lib_name() -> String { 82 | thread_local! { 83 | static LIB_NAME: OnceCell = const { OnceCell::new() }; 84 | } 85 | LIB_NAME.with(|lib_name| { 86 | lib_name 87 | .get_or_init(|| { 88 | let path = ::tarantool::proc::module_path(&LIB_NAME as *const _ as _).unwrap(); 89 | let name = path.file_stem().unwrap(); 90 | name.to_str().unwrap().into() 91 | }) 92 | .clone() 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /tests/src/datetime.rs: -------------------------------------------------------------------------------- 1 | use tarantool::{datetime::Datetime, tuple::Tuple}; 2 | use time_macros::datetime; 3 | 4 | pub fn to_tuple() { 5 | let dt: Datetime = datetime!(2023-11-11 6:10:20.10010 -7).into(); 6 | let t = Tuple::new(&[dt]).unwrap(); 7 | let lua = tarantool::lua_state(); 8 | let result: Datetime = lua.eval_with("return box.tuple.unpack(...)", &t).unwrap(); 9 | assert_eq!(dt, result); 10 | } 11 | 12 | pub fn from_tuple() { 13 | let t: Tuple = tarantool::lua_state() 14 | .eval( 15 | "local dt = require('datetime').parse('2023-11-11T10:11:12.10142+0500') 16 | return box.tuple.new(dt)", 17 | ) 18 | .unwrap(); 19 | let (d,): (Datetime,) = t.decode().unwrap(); 20 | assert_eq!(d.to_string(), "2023-11-11 10:11:12.10142 +05:00:00"); 21 | } 22 | 23 | pub fn to_lua() { 24 | let lua = tarantool::lua_state(); 25 | let dt: Datetime = datetime!(2006-8-13 21:45:13.042069 +3).into(); 26 | let s: String = lua.eval_with("return tostring(...)", &dt).unwrap(); 27 | assert_eq!(s, "2006-08-13T21:45:13.042069+0300"); 28 | } 29 | 30 | pub fn from_lua() { 31 | let d: Datetime = tarantool::lua_state() 32 | .eval("return require('datetime').parse('2023-11-11T10:11:12.10142+0500')") 33 | .unwrap(); 34 | 35 | assert_eq!(d.to_string(), "2023-11-11 10:11:12.10142 +05:00:00"); 36 | } 37 | -------------------------------------------------------------------------------- /tests/src/define_str_enum.rs: -------------------------------------------------------------------------------- 1 | use tarantool::define_str_enum; 2 | use tarantool::msgpack; 3 | use tarantool::tlua; 4 | 5 | /// Ensure the macro supports: 6 | /// - docstrings, 7 | /// - all claimed traits, 8 | /// - deriving other traits (Default in this case). 9 | pub fn basic() { 10 | define_str_enum! { 11 | enum Color { 12 | /// Black as night 13 | Black = "#000000", 14 | /// White as snow 15 | White = "#FFFFFF", 16 | } 17 | } 18 | 19 | impl Default for Color { 20 | fn default() -> Self { 21 | Self::Black 22 | } 23 | } 24 | 25 | assert_eq!(Color::MIN, Color::Black); 26 | assert_eq!(Color::MAX, Color::White); 27 | assert_eq!(Color::VARIANTS.len(), 2); 28 | assert_eq!(Color::default(), Color::Black); 29 | assert_eq!(Color::Black.as_ref(), "#000000"); 30 | assert_eq!(Color::White.as_str(), "#FFFFFF"); 31 | assert_eq!(Color::Black.as_cstr(), tarantool::c_str!("#000000")); 32 | 33 | // PartialEq, PartialOrd 34 | assert_eq!(Color::Black == Color::White, false); 35 | assert_eq!(Color::Black <= Color::White, true); 36 | 37 | // Debug, Display 38 | assert_eq!(format!("{:?}", Color::Black), "Black"); 39 | assert_eq!(format!("{}", Color::White), "#FFFFFF"); 40 | assert_eq!(String::from(Color::White), "#FFFFFF"); 41 | 42 | // std::str::FromStr 43 | use std::str::FromStr; 44 | assert_eq!(Color::from_str("#FFFFFF"), Ok(Color::White)); 45 | assert_eq!(Color::from_str("#000000"), Ok(Color::Black)); 46 | assert_eq!( 47 | Color::from_str("#ffffff").unwrap_err().to_string(), 48 | r##"unknown variant "#ffffff" of enum `Color`, expected one of: ["#000000", "#FFFFFF"]"## 49 | ); 50 | 51 | // serde: ser 52 | assert_eq!(serde_json::to_string(&Color::White).unwrap(), "\"#FFFFFF\""); 53 | 54 | // serde: de 55 | let de = |v| -> Result { serde_json::from_str(v) }; 56 | assert_eq!(de("\"#FFFFFF\"").unwrap(), Color::White); 57 | assert_eq!( 58 | de("\"#00ff00\"").unwrap_err().to_string(), 59 | r##"unknown variant "#00ff00" of enum `Color`, expected one of: ["#000000", "#FFFFFF"] at line 1 column 9"## 60 | ); 61 | 62 | // encode 63 | let white_mp = msgpack::encode(&"#FFFFFF"); 64 | assert_eq!(msgpack::encode(&Color::White), white_mp); 65 | 66 | // decode 67 | assert_eq!(msgpack::decode::(&white_mp).unwrap(), Color::White); 68 | let green_mp = msgpack::encode(&"#00FF00"); 69 | assert_eq!( 70 | msgpack::decode::(&green_mp) 71 | .unwrap_err() 72 | .to_string(), 73 | "failed decoding tarantool_module_test_runner::define_str_enum::basic::Color: unknown enum variant `#00FF00`, expected on of [\"#000000\", \"#FFFFFF\"]" .to_string(), 74 | ); 75 | 76 | // Lua-related traits 77 | let lua = tarantool::lua_state(); 78 | assert_eq!(lua.eval::("return '#FFFFFF'").unwrap(), Color::White); 79 | assert_eq!( 80 | lua.eval_with::<_, String>("return ...", Color::Black) 81 | .unwrap(), 82 | "#000000" 83 | ); 84 | assert_eq!( 85 | lua.eval::("return '#808080'") 86 | .unwrap_err() 87 | .to_string(), 88 | format!( 89 | "failed reading string enum: one of {:?} expected, got string '#808080' 90 | while reading value(s) returned by Lua: {} expected, got string", 91 | Color::values(), 92 | std::any::type_name::(), 93 | ) 94 | ); 95 | 96 | // other claimed traits 97 | impl AssertImpl<'_, L> for Color {} 98 | #[allow(unused)] 99 | trait AssertImpl<'de, L: tlua::AsLua>: 100 | AsRef 101 | + Into 102 | + Into<&'static str> 103 | + Default 104 | + Clone 105 | + Copy 106 | + Eq 107 | + Ord 108 | + PartialEq 109 | + PartialOrd 110 | + std::fmt::Debug 111 | + std::fmt::Display 112 | + std::ops::Deref 113 | + std::hash::Hash 114 | + serde::Deserialize<'de> 115 | + serde::Serialize 116 | + tlua::LuaRead 117 | + tlua::Push 118 | + tlua::PushInto 119 | + tlua::PushOne 120 | + tlua::PushOneInto 121 | { 122 | } 123 | } 124 | 125 | pub fn coerce_from_str() { 126 | define_str_enum! { 127 | #![coerce_from_str] 128 | enum Season { 129 | Summer = "summer", 130 | } 131 | } 132 | 133 | use std::str::FromStr; 134 | assert_eq!(Season::from_str("summer"), Ok(Season::Summer)); 135 | assert_eq!(Season::from_str("SummeR"), Ok(Season::Summer)); 136 | assert_eq!(Season::from_str("SUMMER"), Ok(Season::Summer)); 137 | assert_eq!(Season::from_str(" SUMMER "), Ok(Season::Summer)); 138 | } 139 | 140 | pub fn deserialize_from_owned() { 141 | define_str_enum! { 142 | enum Season { 143 | Summer = "summer", 144 | } 145 | } 146 | 147 | let value = rmpv::Value::String("summer".into()); 148 | let season = 149 | rmpv::ext::from_value::(value).expect("fails to deserialize from owned string"); 150 | assert_eq!(season, Season::Summer); 151 | } 152 | -------------------------------------------------------------------------------- /tests/src/fiber/mutex.rs: -------------------------------------------------------------------------------- 1 | use std::{rc::Rc, time::Duration}; 2 | 3 | use tarantool::{ 4 | fiber::{defer_proc, sleep, start, start_proc, Channel, Mutex}, 5 | util::IntoClones, 6 | }; 7 | 8 | pub fn simple() { 9 | let (sr1, sr2) = Rc::new(Mutex::new(0)).into_clones(); 10 | let f = start_proc(move || *sr2.lock() = 69); 11 | assert_eq!(*sr1.lock(), 69); 12 | f.join(); 13 | } 14 | 15 | pub fn try_lock() { 16 | let (sr1, sr2) = Rc::new(Mutex::new(0)).into_clones(); 17 | let f = start_proc(move || { 18 | let mut guard = sr2.lock(); 19 | sleep(Duration::ZERO); 20 | *guard = 420; 21 | }); 22 | assert!(sr1.try_lock().is_none()); 23 | f.join(); 24 | assert_eq!(sr1.try_lock().map(|g| *g), Some(420)) 25 | } 26 | 27 | pub fn debug() { 28 | let m = Mutex::new(0); 29 | 30 | let (file, line, mut guard) = (file!(), line!(), m.lock()); 31 | 32 | let expected = if cfg!(debug_assertions) { 33 | format!("Mutex {{ data: , .. }}") 34 | } else { 35 | "Mutex { data: , .. }".to_owned() 36 | }; 37 | let mutex_debug_repr = start(|| format!("{:?}", m)).join(); 38 | assert_eq!(mutex_debug_repr, expected); 39 | 40 | *guard = 13; 41 | drop(guard); 42 | 43 | assert_eq!( 44 | start(|| format!("{:?}", m)).join(), 45 | "Mutex { data: 13, .. }" 46 | ); 47 | } 48 | 49 | pub fn advanced() { 50 | let (log0, log1, log2, log3, log_out) = Channel::new(14).into_clones(); 51 | let shared_resource = Rc::new(Mutex::new(vec![])); 52 | let (sr0, sr1, sr2, sr3) = shared_resource.into_clones(); 53 | 54 | let f1 = defer_proc(move || { 55 | log1.send("f1:lock").unwrap(); 56 | let mut guard = sr1.lock(); // Acquire the lock 57 | log1.send("f1:critical").unwrap(); 58 | sleep(Duration::ZERO); // Tease the other fibers 59 | guard.push(1); // Critical section 60 | log1.send("f1:release").unwrap(); 61 | }); 62 | 63 | let f2 = defer_proc(move || { 64 | log2.send("f2:lock").unwrap(); 65 | let mut guard = sr2.lock(); // Acquire the lock 66 | log2.send("f2:critical").unwrap(); 67 | sleep(Duration::ZERO); // Tease the other fibers 68 | guard.push(2); // Critical section 69 | log2.send("f2:release").unwrap(); 70 | }); 71 | 72 | let f3 = defer_proc(move || { 73 | log3.send("f3:lock").unwrap(); 74 | let mut guard = sr3.lock(); // Acquire the lock 75 | log3.send("f3:critical").unwrap(); 76 | sleep(Duration::ZERO); // Tease the other fibers 77 | guard.push(3); // Critical section 78 | log3.send("f3:release").unwrap(); 79 | }); 80 | 81 | log0.send("main:sleep").unwrap(); 82 | sleep(Duration::ZERO); 83 | 84 | log0.send("main:join(f2)").unwrap(); 85 | f2.join(); 86 | log0.send("main:join(f1)").unwrap(); 87 | f1.join(); 88 | log0.send("main:join(f3)").unwrap(); 89 | f3.join(); 90 | log0.send("main:done").unwrap(); 91 | 92 | assert_eq!(Rc::try_unwrap(sr0).unwrap().into_inner(), &[1, 2, 3]); 93 | 94 | assert_eq!( 95 | log_out.try_iter().collect::>(), 96 | vec![ 97 | "main:sleep", 98 | "f1:lock", 99 | "f1:critical", 100 | "f2:lock", 101 | "f3:lock", 102 | "main:join(f2)", 103 | "f1:release", 104 | "f2:critical", 105 | "f2:release", 106 | "f3:critical", 107 | "main:join(f1)", 108 | "main:join(f3)", 109 | "f3:release", 110 | "main:done", 111 | ] 112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /tests/src/fiber/old.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::redundant_allocation)] 2 | use std::rc::Rc; 3 | use std::time::Duration; 4 | 5 | use tarantool::fiber::{fiber_yield, is_cancelled, sleep, Cond, Fiber, FiberAttr}; 6 | 7 | pub fn fiber_new() { 8 | let mut fiber = Fiber::new("test_fiber", &mut |_| 0); 9 | fiber.set_joinable(true); 10 | fiber.start(()); 11 | fiber.join(); 12 | } 13 | 14 | pub fn fiber_new_with_attr() { 15 | let mut attr = FiberAttr::new(); 16 | attr.set_stack_size(100_000).unwrap(); 17 | 18 | let mut fiber = Fiber::new_with_attr("test_fiber", &attr, &mut |_| 0); 19 | fiber.set_joinable(true); 20 | fiber.start(()); 21 | fiber.join(); 22 | } 23 | 24 | pub fn fiber_arg() { 25 | let mut fiber = Fiber::new("test_fiber", &mut |x| { 26 | assert_eq!(*x, 99); 27 | 0 28 | }); 29 | fiber.set_joinable(true); 30 | fiber.start(99); 31 | fiber.join(); 32 | } 33 | 34 | pub fn fiber_cancel() { 35 | let mut fiber = Fiber::new("test_fiber", &mut |_| { 36 | assert_eq!(is_cancelled(), false); 37 | sleep(Duration::from_millis(10)); 38 | assert_eq!(is_cancelled(), true); 39 | 0 40 | }); 41 | fiber.set_joinable(true); 42 | fiber.start(()); 43 | fiber.cancel(); 44 | fiber.join(); 45 | } 46 | 47 | pub fn fiber_wake() { 48 | let mut fiber = Fiber::new("test_fiber", &mut |_| { 49 | fiber_yield(); 50 | 0 51 | }); 52 | fiber.set_joinable(true); 53 | fiber.start(()); 54 | sleep(Duration::from_millis(10)); 55 | fiber.wakeup(); 56 | fiber.join(); 57 | } 58 | 59 | pub fn fiber_cond_signal() { 60 | let cond = Rc::new(Cond::new()); 61 | let mut fiber = Fiber::new("test_fiber", &mut |cond: Box>| { 62 | (*cond).wait(); 63 | 0 64 | }); 65 | fiber.set_joinable(true); 66 | fiber.start(cond.clone()); 67 | sleep(Duration::from_millis(10)); 68 | cond.signal(); 69 | fiber.join(); 70 | } 71 | 72 | pub fn fiber_cond_broadcast() { 73 | let cond = Rc::new(Cond::new()); 74 | 75 | let mut fiber_a = Fiber::new("test_fiber_a", &mut |cond: Box>| { 76 | (*cond).wait(); 77 | 0 78 | }); 79 | fiber_a.set_joinable(true); 80 | fiber_a.start(cond.clone()); 81 | 82 | let mut fiber_b = Fiber::new("test_fiber_b", &mut |cond: Box>| { 83 | (*cond).wait(); 84 | 0 85 | }); 86 | fiber_b.set_joinable(true); 87 | fiber_b.start(cond.clone()); 88 | 89 | sleep(Duration::from_millis(10)); 90 | cond.broadcast(); 91 | fiber_a.join(); 92 | fiber_b.join(); 93 | } 94 | 95 | pub fn fiber_cond_timeout() { 96 | let cond = Rc::new(Cond::new()); 97 | let mut fiber = Fiber::new("test_fiber", &mut |cond: Box>| { 98 | let r = (*cond).wait_timeout(Duration::from_millis(10)); 99 | assert!(!r); 100 | 0 101 | }); 102 | fiber.set_joinable(true); 103 | fiber.start(cond.clone()); 104 | sleep(Duration::from_millis(20)); 105 | cond.signal(); 106 | fiber.join(); 107 | } 108 | -------------------------------------------------------------------------------- /tests/src/latch.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::time::Duration; 3 | 4 | use tarantool::fiber; 5 | 6 | pub fn latch_lock() { 7 | let latch = Rc::new(fiber::Latch::new()); 8 | 9 | let fiber = fiber::start(|| { 10 | let _lock = latch.lock(); 11 | fiber::sleep(Duration::from_millis(10)); 12 | }); 13 | 14 | latch.lock(); 15 | fiber.join(); 16 | } 17 | 18 | pub fn latch_try_lock() { 19 | let latch = Rc::new(fiber::Latch::new()); 20 | 21 | let fiber = fiber::start(|| { 22 | let _lock = latch.lock(); 23 | fiber::sleep(Duration::from_millis(10)); 24 | }); 25 | 26 | assert!(latch.try_lock().is_none()); 27 | 28 | fiber.join(); 29 | assert!(latch.try_lock().is_some()); 30 | } 31 | -------------------------------------------------------------------------------- /tests/src/session.rs: -------------------------------------------------------------------------------- 1 | use tarantool::session; 2 | 3 | const GUEST_UID: u32 = 0; 4 | const ADMIN_UID: u32 = 1; 5 | 6 | #[tarantool::test] 7 | pub fn uid() { 8 | let uid = session::uid().unwrap(); 9 | assert_eq!(uid, ADMIN_UID); 10 | } 11 | 12 | #[tarantool::test] 13 | pub fn euid() { 14 | let euid = session::euid().unwrap(); 15 | assert_eq!(euid, ADMIN_UID); 16 | } 17 | 18 | fn cur() -> u32 { 19 | session::uid().unwrap() 20 | } 21 | 22 | #[tarantool::test] 23 | pub fn su() { 24 | assert_eq!(cur(), ADMIN_UID); 25 | 26 | let su = session::su(GUEST_UID).unwrap(); 27 | assert_eq!(cur(), GUEST_UID); 28 | 29 | drop(su); 30 | 31 | assert_eq!(cur(), ADMIN_UID); 32 | } 33 | 34 | #[tarantool::test] 35 | pub fn with_su() { 36 | assert_eq!(cur(), ADMIN_UID); 37 | 38 | session::with_su(GUEST_UID, || { 39 | assert_eq!(cur(), GUEST_UID); 40 | }) 41 | .unwrap(); 42 | 43 | assert_eq!(cur(), ADMIN_UID); 44 | } 45 | 46 | #[cfg(feature = "picodata")] 47 | #[tarantool::test] 48 | pub fn user_id_by_name() { 49 | assert_eq!(session::user_id_by_name("guest").unwrap(), GUEST_UID); 50 | assert_eq!(session::user_id_by_name("admin").unwrap(), ADMIN_UID); 51 | } 52 | -------------------------------------------------------------------------------- /tests/src/test_attr.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::assertions_on_constants)] 2 | use tarantool::test::TestCase; 3 | 4 | #[tarantool::test] 5 | pub fn just_works() { 6 | assert!(true); 7 | } 8 | 9 | #[tarantool::test(should_panic)] 10 | pub fn should_panic() { 11 | assert!(false); 12 | } 13 | 14 | #[::linkme::distributed_slice] 15 | static TEST_ATTR_SECTION: [TestCase] = [..]; 16 | 17 | #[tarantool::test(section = "crate::test_attr::TEST_ATTR_SECTION")] 18 | pub fn with_custom_section() { 19 | let test_names = TEST_ATTR_SECTION.iter().collect::>(); 20 | assert_eq!( 21 | test_names, 22 | [&TestCase::new( 23 | "tarantool_module_test_runner::test_attr::with_custom_section", 24 | with_custom_section, 25 | false, 26 | )] 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /tests/src/tlua/lua_tables.rs: -------------------------------------------------------------------------------- 1 | use tarantool::tlua::{function0, Lua, LuaTable, PushGuard}; 2 | 3 | pub fn iterable() { 4 | let lua = Lua::new(); 5 | 6 | lua.exec("a = { 9, 8, 7 }").unwrap(); 7 | 8 | let table = lua.get::, _>("a").unwrap(); 9 | let mut counter = 0; 10 | 11 | for (key, value) in table.iter().flatten() { 12 | let _: u32 = key; 13 | let _: u32 = value; 14 | assert_eq!(key + value, 10); 15 | counter += 1; 16 | } 17 | 18 | assert_eq!(counter, 3); 19 | } 20 | 21 | pub fn iterable_multipletimes() { 22 | let lua = Lua::new(); 23 | 24 | lua.exec("a = { 9, 8, 7 }").unwrap(); 25 | 26 | let table = lua.get::, _>("a").unwrap(); 27 | 28 | for _ in 0..10 { 29 | let table_content: Vec> = table.iter().map(Result::ok).collect(); 30 | assert_eq!( 31 | table_content, 32 | vec![Some((1, 9)), Some((2, 8)), Some((3, 7))] 33 | ); 34 | } 35 | } 36 | 37 | pub fn get_set() { 38 | let lua = Lua::new(); 39 | 40 | lua.exec("a = { 9, 8, 7 }").unwrap(); 41 | let table = lua.get::, _>("a").unwrap(); 42 | 43 | let x: i32 = table.get(2).unwrap(); 44 | assert_eq!(x, 8); 45 | 46 | table.set(3, "hello"); 47 | let y: String = table.get(3).unwrap(); 48 | assert_eq!(y, "hello"); 49 | 50 | let z: i32 = table.get(1).unwrap(); 51 | assert_eq!(z, 9); 52 | } 53 | 54 | pub fn get_nil() { 55 | let lua = Lua::new(); 56 | let t: LuaTable<_> = lua.eval("return {}").unwrap(); 57 | let res = t.try_get::<_, i32>(1); 58 | assert_eq!( 59 | res.unwrap_err().to_string(), 60 | "failed reading value from Lua table: i32 expected, got nil" 61 | ); 62 | assert_eq!(t.get::, _>(1), Some(None)); 63 | assert_eq!(t.get::>, _>(1), Some(None)); 64 | } 65 | 66 | pub fn table_over_table() { 67 | let lua = Lua::new(); 68 | 69 | lua.exec("a = { 9, { 8, 7 }, 6 }").unwrap(); 70 | let table = lua.get::, _>("a").unwrap(); 71 | 72 | let x: i32 = table.get(1).unwrap(); 73 | assert_eq!(x, 9); 74 | 75 | { 76 | let subtable = table.get::, _>(2).unwrap(); 77 | 78 | let y: i32 = subtable.get(1).unwrap(); 79 | assert_eq!(y, 8); 80 | 81 | let z: i32 = subtable.get(2).unwrap(); 82 | assert_eq!(z, 7); 83 | } 84 | 85 | let w: i32 = table.get(3).unwrap(); 86 | assert_eq!(w, 6); 87 | } 88 | 89 | pub fn get_or_create_metatable() { 90 | let lua = Lua::new(); 91 | 92 | lua.exec("a = { 9, 8, 7 }").unwrap(); 93 | 94 | { 95 | let table = lua.get::, _>("a").unwrap(); 96 | 97 | #[allow(deprecated)] 98 | let metatable = table.get_or_create_metatable(); 99 | fn handler() -> i32 { 100 | 5 101 | } 102 | metatable.set("__add", function0(handler)); 103 | } 104 | 105 | let r: i32 = lua.eval("return a + a").unwrap(); 106 | assert_eq!(r, 5); 107 | } 108 | 109 | pub fn complex_anonymous_table_metatable() { 110 | let lua = Lua::new(); 111 | lua.openlibs(); 112 | 113 | let table = LuaTable::empty(&lua); 114 | let return_value = "sample_result"; 115 | 116 | { 117 | let mt = table.metatable(); 118 | 119 | let mt_methods = LuaTable::empty(&lua); 120 | mt_methods.set("example_method", function0(move || return_value)); 121 | mt.set("__index", &mt_methods); 122 | 123 | // ensure we can push metatable itself on stack. 124 | table.set("mt", &mt); 125 | }; 126 | 127 | let r: String = lua 128 | .eval_with( 129 | "assert((...).mt == getmetatable(...)); return (...):example_method()", 130 | &table, 131 | ) 132 | .unwrap(); 133 | assert_eq!(&r, return_value); 134 | } 135 | 136 | pub fn empty_array() { 137 | let lua = Lua::new(); 138 | 139 | { 140 | let array = lua.empty_array("a"); 141 | array.set("b", 3) 142 | } 143 | 144 | let table: LuaTable<_> = lua.get("a").unwrap(); 145 | assert_eq!(3, table.get::("b").unwrap()); 146 | } 147 | 148 | pub fn by_value() { 149 | let lua = Lua::new(); 150 | 151 | { 152 | let array = lua.empty_array("a"); 153 | { 154 | let array2 = array.empty_array("b"); 155 | array2.set("c", 3); 156 | } 157 | } 158 | 159 | type Lua = tarantool::tlua::TempLua; 160 | let table: LuaTable> = lua.into_get("a").ok().unwrap(); 161 | let table2: LuaTable>>> = table.into_get("b").ok().unwrap(); 162 | assert_eq!(3, table2.get::("c").unwrap()); 163 | let table: LuaTable> = table2.into_inner().into_inner(); 164 | // do it again to make sure the stack is still sane 165 | let table2: LuaTable>>> = table.into_get("b").ok().unwrap(); 166 | assert_eq!(3, table2.get::("c").unwrap()); 167 | let table: LuaTable> = table2.into_inner().into_inner(); 168 | let _lua: Lua = table.into_inner().into_inner(); 169 | } 170 | 171 | pub fn registry() { 172 | let lua = Lua::new(); 173 | 174 | let table = LuaTable::registry(&lua); 175 | table.set("3", "hello"); 176 | let y: String = table.get("3").unwrap(); 177 | assert_eq!(y, "hello"); 178 | } 179 | 180 | pub fn registry_metatable() { 181 | let lua = Lua::new(); 182 | 183 | let registry = LuaTable::registry(&lua); 184 | #[allow(deprecated)] 185 | let metatable = registry.get_or_create_metatable(); 186 | metatable.set(3, "hello"); 187 | } 188 | 189 | pub fn table_iter_stack_invariance() { 190 | let lua = Lua::new(); 191 | let table_of_tables: LuaTable<_> = lua.eval("return {{1}, {2}, {3}}").unwrap(); 192 | // Here we are attempting to create an Vec> by iterating over 193 | // the nested LuaTable. This is not allowed, because the lua stack must 194 | // conform to an invariant while iterating over a lua table: before each 195 | // iteration the stack must have the same number of elements and the top 196 | // element must be a table key, so the value returned by `lua_next` must be 197 | // removed from the stack. But LuaTable instance requires the underlying 198 | // table to stay on the stack for it's lifetime, which means that it must be 199 | // dropped before next iteration. And if we try to collect the LuaTable 200 | // instances created during iteration, this will break the stack invariance. 201 | let _vec_of_tables = table_of_tables 202 | .iter::>() 203 | .flatten() 204 | .map(|(_, t)| t) 205 | .collect::>(); 206 | } 207 | 208 | pub fn iter_table_of_tables() { 209 | let lua = Lua::new(); 210 | 211 | let t: LuaTable<_> = lua 212 | .eval( 213 | "return { 214 | { f = function(self) return 'hello ' .. self.x end, x = 'world' }, 215 | { f = function() return 'goodbye' end }, 216 | { f = function(self) return '' .. self.v end, v = 69 } 217 | }", 218 | ) 219 | .unwrap(); 220 | let mut res = Vec::::new(); 221 | for (_, t) in t.iter::>().flatten() { 222 | res.push(t.call_method("f", ()).unwrap()); 223 | } 224 | assert_eq!( 225 | res, 226 | vec![ 227 | "hello world".to_string(), 228 | "goodbye".to_string(), 229 | "69".to_string() 230 | ] 231 | ); 232 | } 233 | -------------------------------------------------------------------------------- /tests/src/tlua/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::let_unit_value)] 2 | pub mod any; 3 | pub mod functions_write; 4 | pub mod lua_functions; 5 | pub mod lua_tables; 6 | pub mod misc; 7 | pub mod object; 8 | pub mod rust_tables; 9 | pub mod userdata; 10 | pub mod values; 11 | -------------------------------------------------------------------------------- /tests/src/tlua/object.rs: -------------------------------------------------------------------------------- 1 | use tarantool::tlua::{ 2 | AnyLuaString, Call, Callable, Index, Indexable, IndexableRW, LuaTable, NewIndex, Nil, Object, 3 | }; 4 | 5 | use crate::common::LuaStackIntegrityGuard; 6 | 7 | use std::convert::TryFrom; 8 | 9 | pub fn callable_builtin() { 10 | let lua = tarantool::lua_state(); 11 | let c: Callable<_> = lua.eval("return function(x) return x + 1 end").unwrap(); 12 | assert_eq!(c.call_with(1).ok(), Some(2)); 13 | } 14 | 15 | pub fn callable_ffi() { 16 | let lua = tarantool::lua_state(); 17 | let c: Callable<_> = lua 18 | .eval( 19 | " 20 | require 'ffi'.cdef[[ int atoi(const char *); ]] 21 | return require 'ffi'.C.atoi 22 | ", 23 | ) 24 | .unwrap(); 25 | assert_eq!(c.call_with("420").ok(), Some(420)); 26 | } 27 | 28 | pub fn callable_meta() { 29 | let lua = tarantool::lua_state(); 30 | let c: Callable<_> = lua 31 | .eval( 32 | " 33 | return setmetatable( 34 | { a = 1, b = 'hello' }, 35 | { __call = function(self, key) return self[key] end } 36 | ) 37 | ", 38 | ) 39 | .unwrap(); 40 | assert_eq!(c.call_with("a").ok(), Some(1)); 41 | assert_eq!(c.call_with("b").ok(), Some("hello".to_string())); 42 | assert_eq!(c.call().ok(), Some(Nil)); 43 | } 44 | 45 | pub fn indexable_builtin() { 46 | let lua = tarantool::lua_state(); 47 | let i: Indexable<_> = lua.eval("return { [1] = 'one', one = 1 }").unwrap(); 48 | assert_eq!(i.get(1), Some("one".to_string())); 49 | assert_eq!(i.get("one"), Some(1)); 50 | assert_eq!(i.get(2), Some(Nil)); 51 | assert_eq!(i.get(2), None::); 52 | assert_eq!( 53 | i.try_get::<_, i32>(2).unwrap_err().to_string(), 54 | "failed reading value from Lua table: i32 expected, got nil" 55 | ); 56 | } 57 | 58 | #[rustfmt::skip] 59 | pub fn indexable_ffi() { 60 | let lua = tarantool::lua_state(); 61 | let _stack_integrity_guard = LuaStackIntegrityGuard::new("indexable stack check", &lua); 62 | { 63 | let i: Indexable<_> = lua.eval(" 64 | local ffi = require('ffi') 65 | ffi.cdef([[ 66 | struct bigfoo_t { 67 | int nice; 68 | int array[3]; 69 | } 70 | ]]) 71 | return ffi.new( 72 | 'struct bigfoo_t', 73 | { 74 | nice = 69, 75 | array = { 1, 2, 3 } 76 | } 77 | ) 78 | ").unwrap(); 79 | assert_eq!(i.get("nice"), Some(69)); 80 | let array = i.get::<_, Indexable<_>>("array").unwrap(); 81 | assert_eq!(array.get(0), Some(1)); 82 | assert_eq!(array.get(1), Some(2)); 83 | assert_eq!(array.get(2), Some(3)); 84 | assert_eq!(i.get("no such member"), None::<()>); 85 | assert_eq!( 86 | i.try_get::<_, ()>("no such member").unwrap_err().to_string(), 87 | "'struct bigfoo_t' has no member named 'no such member'" 88 | ); 89 | } 90 | } 91 | 92 | #[rustfmt::skip] 93 | pub fn indexable_meta() { 94 | let lua = tarantool::lua_state(); 95 | let (file, line) = (file!(), line!() + 1); // should point `lua.eval` next line 96 | let i: Indexable<_> = lua.eval(" 97 | return setmetatable( 98 | { 1 }, 99 | { 100 | __index = function(self, key) 101 | local res = {} 102 | for i = 1,key do 103 | res[i] = self[1] + i 104 | end 105 | return res 106 | end 107 | } 108 | ) 109 | ").unwrap(); 110 | assert_eq!(i.get(1), Some(1)); 111 | assert_eq!(i.get(2), Some([2, 3])); 112 | assert_eq!(i.get(3), Some([2, 3, 4])); 113 | assert_eq!(i.get("hello"), None::<()>); 114 | assert_eq!( 115 | i.try_get::<_, u8>("hello").unwrap_err().to_string(), 116 | format!("[{file}:{line}]:7: 'for' limit must be a number") 117 | ); 118 | 119 | let t = LuaTable::try_from(Object::from(i)).unwrap(); 120 | assert_eq!( 121 | t.try_get::<_, u8>("hello").unwrap_err().to_string(), 122 | format!("[{file}:{line}]:7: 'for' limit must be a number") 123 | ); 124 | } 125 | 126 | pub fn cannot_get_mutltiple_values() { 127 | let lua = tarantool::lua_state(); 128 | let i: Indexable<_> = lua.eval("return { 'one' };").unwrap(); 129 | assert_eq!(i.get::<_, String>(1), Some("one".to_string())); 130 | assert_eq!(i.get::<_, (String,)>(1), Some(("one".to_string(),))); 131 | assert_eq!( 132 | i.try_get::<_, (String, i32)>(1).unwrap_err().to_string(), 133 | "failed reading Lua value: i32 expected, got no value 134 | while reading one of multiple values: i32 at index 2 (1-based) expected, got no value 135 | while reading value from Lua table: (alloc::string::String, i32) expected, got string" 136 | ); 137 | assert_eq!( 138 | i.try_get::<_, (i32, i32)>(2).unwrap_err().to_string(), 139 | "failed reading Lua value: i32 expected, got nil 140 | while reading one of multiple values: i32 at index 1 (1-based) expected, got incorrect value 141 | while reading value from Lua table: (i32, i32) expected, got nil" 142 | ); 143 | } 144 | 145 | pub fn indexable_rw_builtin() { 146 | let lua = tarantool::lua_state(); 147 | let i: IndexableRW<_> = lua.eval("return {}").unwrap(); 148 | i.set(1, "foo"); 149 | assert_eq!(i.get(1), Some("foo".to_string())); 150 | i.set("nice", 69); 151 | assert_eq!(i.get("nice"), Some(69)); 152 | } 153 | 154 | #[rustfmt::skip] 155 | pub fn indexable_rw_meta() { 156 | let lua = tarantool::lua_state(); 157 | let (file, line) = (file!(), line!() + 1); // should point `lua.eval` next line 158 | let i: IndexableRW<_> = lua.eval(" 159 | return setmetatable({}, { __newindex = 160 | function(self, k, v) 161 | rawset(self, k, 'super_' .. v) 162 | end 163 | }) 164 | ").unwrap(); 165 | i.set(1, "foo"); 166 | assert_eq!(i.get(1), Some("super_foo".to_string())); 167 | i.set(2, 69); 168 | assert_eq!(i.get(2), Some("super_69".to_string())); 169 | i.set(1, "foo"); 170 | assert_eq!(i.get(1), Some("foo".to_string())); 171 | assert_eq!( 172 | i.try_set(3, [1, 2, 3]).unwrap_err().to_string(), 173 | format!( 174 | "[{file}:{line}]:4: \ 175 | attempt to concatenate local 'v' (a table value)" 176 | ) 177 | ); 178 | assert_eq!(i.get(3), None::); 179 | } 180 | 181 | pub fn anything_to_msgpack() { 182 | let lua = tarantool::lua_state(); 183 | let o: Object<_> = lua.eval("return {69, foo='bar'}").unwrap(); 184 | let mp: AnyLuaString = lua 185 | .eval_with("return require'msgpack'.encode(...)", &o) 186 | .unwrap(); 187 | assert_eq!(mp.as_bytes(), b"\x82\x01\x45\xa3foo\xa3bar"); 188 | } 189 | -------------------------------------------------------------------------------- /tests/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use tarantool::error::Error; 4 | use tarantool::space::Space; 5 | use tarantool::transaction::transaction; 6 | 7 | use crate::common::S1Record; 8 | 9 | pub fn transaction_commit() { 10 | let space = Space::find("test_s1").unwrap(); 11 | space.truncate().unwrap(); 12 | 13 | let input = S1Record { 14 | id: 1, 15 | text: "test".to_string(), 16 | }; 17 | 18 | let result = transaction(|| -> Result<(), Error> { 19 | space.insert(&input)?; 20 | Ok(()) 21 | }); 22 | assert!(result.is_ok()); 23 | 24 | let output = space.get(&(1,)).unwrap(); 25 | assert!(output.is_some()); 26 | assert_eq!(output.unwrap().decode::().unwrap(), input); 27 | } 28 | 29 | pub fn transaction_rollback() { 30 | let space = Space::find("test_s1").unwrap(); 31 | space.truncate().unwrap(); 32 | 33 | let result = transaction(|| -> Result<(), Error> { 34 | space.insert(&S1Record { 35 | id: 1, 36 | text: "test".to_string(), 37 | })?; 38 | Err(Error::IO(io::ErrorKind::Interrupted.into())) 39 | }); 40 | assert_eq!( 41 | result.unwrap_err().to_string(), 42 | "transaction rolled-back: io error: operation interrupted" 43 | ); 44 | 45 | let output = space.get(&(1,)).unwrap(); 46 | assert!(output.is_none()); 47 | } 48 | -------------------------------------------------------------------------------- /tests/src/tuple_picodata.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "picodata")] 2 | 3 | use tarantool::tuple::{FieldType, KeyDef, KeyDefPart, Tuple}; 4 | 5 | pub fn tuple_hash() { 6 | let tuple = Tuple::new(&(1, 2, 3)).unwrap(); 7 | let key = KeyDef::new(vec![ 8 | &KeyDefPart { 9 | field_no: 0, 10 | field_type: FieldType::Integer, 11 | ..Default::default() 12 | }, 13 | &KeyDefPart { 14 | field_no: 1, 15 | field_type: FieldType::Integer, 16 | ..Default::default() 17 | }, 18 | ]) 19 | .unwrap(); 20 | assert_eq!(key.hash(&tuple), 605624609); 21 | 22 | let tuple = Tuple::new(&(1,)).unwrap(); 23 | let key = KeyDef::new(vec![&KeyDefPart { 24 | field_no: 0, 25 | field_type: FieldType::Integer, 26 | ..Default::default() 27 | }]) 28 | .unwrap(); 29 | assert_eq!(key.hash(&tuple), 1457374933); 30 | 31 | let tuple = Tuple::new(&(1,)).unwrap(); 32 | let key = KeyDef::new(vec![ 33 | &KeyDefPart { 34 | field_no: 0, 35 | field_type: FieldType::Integer, 36 | ..Default::default() 37 | }, 38 | &KeyDefPart { 39 | field_no: 1, 40 | field_type: FieldType::Integer, 41 | is_nullable: true, 42 | ..Default::default() 43 | }, 44 | ]) 45 | .unwrap(); 46 | assert_eq!(key.hash(&tuple), 766361540); 47 | } 48 | -------------------------------------------------------------------------------- /tests/src/uuid.rs: -------------------------------------------------------------------------------- 1 | use tarantool::{tlua::LuaFunction, tuple::Tuple, uuid::Uuid}; 2 | 3 | const UUID_STR: &str = "30de7784-33e2-4393-a8cd-b67534db2432"; 4 | 5 | pub fn to_tuple() { 6 | let u = Uuid::parse_str(UUID_STR).unwrap(); 7 | let t = Tuple::new(&[u]).unwrap(); 8 | let lua = tarantool::lua_state(); 9 | let f: LuaFunction<_> = lua.eval("return box.tuple.unpack").unwrap(); 10 | let u: Uuid = f.call_with_args(&t).unwrap(); 11 | assert_eq!(u.to_string(), UUID_STR); 12 | } 13 | 14 | pub fn from_tuple() { 15 | let t: Tuple = tarantool::lua_state() 16 | .eval(&format!( 17 | "return box.tuple.new(require('uuid').fromstr('{}'))", 18 | UUID_STR 19 | )) 20 | .unwrap(); 21 | let (u,): (Uuid,) = t.decode().unwrap(); 22 | assert_eq!(u.to_string(), UUID_STR); 23 | } 24 | 25 | pub fn to_lua() { 26 | let uuid: Uuid = tarantool::lua_state() 27 | .eval(&format!("return require('uuid').fromstr('{}')", UUID_STR)) 28 | .unwrap(); 29 | assert_eq!(uuid.to_string(), UUID_STR); 30 | } 31 | 32 | pub fn from_lua() { 33 | let uuid = Uuid::parse_str(UUID_STR).unwrap(); 34 | let lua = tarantool::lua_state(); 35 | let tostring: LuaFunction<_> = lua.eval("return tostring").unwrap(); 36 | let s: String = tostring.call_with_args(uuid).unwrap(); 37 | assert_eq!(s, UUID_STR); 38 | } 39 | -------------------------------------------------------------------------------- /tlua-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Georgy Moshkin ", 4 | ] 5 | name = "tlua-derive" 6 | description = "Tlua derive macro definitions" 7 | version = "1.0.2" 8 | edition = "2018" 9 | license = "BSD-2-Clause" 10 | documentation = "https://docs.rs/tlua-derive/" 11 | repository = "https://github.com/picodata/tarantool-module" 12 | rust-version = "1.82" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | syn = { version = "1.0.98", features = ["full", "visit"] } 19 | quote = "^1.0" 20 | proc-macro2 = "^1.0" 21 | 22 | -------------------------------------------------------------------------------- /tlua/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tlua" 3 | version = "7.0.0" 4 | edition = "2018" 5 | authors = [ 6 | "Georgy Moshkin ", 7 | "Egor Ivkov ", 8 | "pierre.krieger1708@gmail.com", 9 | ] 10 | description = "Zero-cost high-level wrapper for Tarantool-LuaJIT" 11 | keywords = ["lua"] 12 | repository = "https://github.com/picodata/tarantool-module" 13 | documentation = "http://docs.rs/tlua" 14 | license = "MIT" 15 | rust-version = "1.82" 16 | 17 | [dependencies] 18 | libc = "0.2" 19 | tlua-derive = { path = "../tlua-derive", version = "1.0.2" } 20 | serde = { version = "1.0", features = ["derive"] } 21 | linkme = { version = "0.3", optional = true } 22 | tester = { version = "0.7.0", optional = true } 23 | thiserror = "1.0.30" 24 | 25 | [features] 26 | internal_test = ["linkme", "tester"] 27 | -------------------------------------------------------------------------------- /tlua/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Pierre Krieger 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /tlua/src/any.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroI32; 2 | 3 | use crate::{ 4 | AsLua, LuaRead, LuaTable, Nil, Push, PushGuard, PushInto, PushOne, PushOneInto, ReadResult, 5 | Void, 6 | }; 7 | 8 | #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 9 | pub struct AnyLuaString(pub Vec); 10 | 11 | impl AnyLuaString { 12 | pub fn as_bytes(&self) -> &[u8] { 13 | self.0.as_slice() 14 | } 15 | } 16 | 17 | /// Represents any value that can be stored by Lua 18 | #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 19 | pub enum AnyHashableLuaValue { 20 | // TODO(gmoshkin): remove Lua prefix 21 | LuaString(String), 22 | LuaAnyString(AnyLuaString), 23 | LuaNumber(i32), 24 | // TODO(gmoshkin): True, False 25 | LuaBoolean(bool), 26 | LuaArray(Vec<(AnyHashableLuaValue, AnyHashableLuaValue)>), 27 | LuaNil, 28 | 29 | /// The "Other" element is (hopefully) temporary and will be replaced by "Function" and "Userdata". 30 | /// A panic! will trigger if you try to push a Other. 31 | LuaOther, 32 | } 33 | 34 | /// Represents any value that can be stored by Lua 35 | #[derive(Clone, Debug, PartialEq)] 36 | pub enum AnyLuaValue { 37 | // TODO(gmoshkin): remove Lua prefix 38 | LuaString(String), 39 | LuaAnyString(AnyLuaString), 40 | LuaNumber(f64), 41 | // TODO(gmoshkin): True, False 42 | LuaBoolean(bool), 43 | LuaArray(Vec<(AnyLuaValue, AnyLuaValue)>), 44 | LuaNil, 45 | 46 | /// The "Other" element is (hopefully) temporary and will be replaced by "Function" and "Userdata". 47 | /// A panic! will trigger if you try to push a Other. 48 | LuaOther, 49 | } 50 | 51 | macro_rules! impl_any_lua_value { 52 | (@push $self:expr, $lua:expr, $push:ident) => { 53 | Ok(match $self { 54 | Self::LuaString(val) => val.$push($lua), 55 | Self::LuaAnyString(val) => val.$push($lua), 56 | Self::LuaNumber(val) => val.$push($lua), 57 | Self::LuaBoolean(val) => val.$push($lua), 58 | Self::LuaArray(val) => val.$push($lua), 59 | Self::LuaNil => Nil.$push($lua), 60 | Self::LuaOther => panic!("can't push a AnyLuaValue of type Other"), 61 | }) 62 | }; 63 | ($t:ty) => { 64 | impl Push for $t { 65 | type Err = Void; // TODO: use `!` instead (https://github.com/rust-lang/rust/issues/35121) 66 | 67 | #[inline] 68 | fn push_to_lua(&self, lua: L) -> Result, (Void, L)> { 69 | impl_any_lua_value!(@push self, lua, push_no_err) 70 | } 71 | } 72 | 73 | impl PushOne for $t {} 74 | 75 | impl PushInto for $t { 76 | type Err = Void; // TODO: use `!` instead (https://github.com/rust-lang/rust/issues/35121) 77 | 78 | #[inline] 79 | fn push_into_lua(self, lua: L) -> Result, (Void, L)> { 80 | impl_any_lua_value!(@push self, lua, push_into_no_err) 81 | } 82 | } 83 | 84 | impl PushOneInto for $t {} 85 | 86 | impl LuaRead for $t { 87 | #[inline] 88 | fn lua_read_at_position(lua: L, index: NonZeroI32) -> ReadResult { 89 | let lua = match LuaRead::lua_read_at_position(lua, index) { 90 | Ok(v) => return Ok(Self::LuaString(v)), 91 | Err((lua, _)) => lua, 92 | }; 93 | 94 | let lua = match LuaRead::lua_read_at_position(lua, index) { 95 | Ok(v) => return Ok(Self::LuaAnyString(v)), 96 | Err((lua, _)) => lua, 97 | }; 98 | 99 | let lua = match LuaRead::lua_read_at_position(lua, index) { 100 | Ok(v) => return Ok(Self::LuaNumber(v)), 101 | Err((lua, _)) => lua, 102 | }; 103 | 104 | let lua = match LuaRead::lua_read_at_position(lua, index) { 105 | Ok(v) => return Ok(Self::LuaBoolean(v)), 106 | Err((lua, _)) => lua, 107 | }; 108 | 109 | let lua = match LuaRead::lua_read_at_position(lua, index) { 110 | Ok(v) => return Ok(Self::LuaString(v)), 111 | Err((lua, _)) => lua, 112 | }; 113 | 114 | let lua = match LuaRead::lua_read_at_position(lua, index) { 115 | Ok(v) => return Ok(Self::LuaAnyString(v)), 116 | Err((lua, _)) => lua, 117 | }; 118 | 119 | let lua = match Nil::lua_read_at_position(lua, index) { 120 | Ok(Nil) => return Ok(Self::LuaNil), 121 | Err((lua, _)) => lua, 122 | }; 123 | 124 | let _ = match LuaTable::lua_read_at_position(lua.as_lua(), index) { 125 | Ok(v) => return Ok( 126 | Self::LuaArray(v.iter::().flatten().collect()) 127 | ), 128 | Err((lua, _)) => lua, 129 | }; 130 | 131 | Ok(Self::LuaOther) 132 | } 133 | } 134 | } 135 | } 136 | 137 | impl_any_lua_value! {AnyLuaValue} 138 | impl_any_lua_value! {AnyHashableLuaValue} 139 | -------------------------------------------------------------------------------- /tlua/src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::{c_ptr, ffi, AsLua, LuaState, Push, PushGuard, PushOne, Void}; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Debug)] 7 | enum ValueOnTheStack { 8 | Absolute(i32), 9 | Relative(i32), 10 | } 11 | 12 | impl Push for ValueOnTheStack { 13 | type Err = Void; 14 | 15 | fn push_to_lua(&self, lua: L) -> Result, (Void, L)> { 16 | let index = match self { 17 | Self::Absolute(index) | Self::Relative(index) => index, 18 | }; 19 | unsafe { 20 | ffi::lua_pushvalue(lua.as_lua(), *index); 21 | Ok(PushGuard::new(lua, 1)) 22 | } 23 | } 24 | } 25 | 26 | impl PushOne for ValueOnTheStack {} 27 | 28 | #[allow(clippy::missing_safety_doc)] 29 | pub unsafe fn dump_stack_raw_to(lua: LuaState, mut out: impl Write) -> std::io::Result<()> { 30 | let top = ffi::lua_gettop(lua); 31 | ffi::luaopen_base(lua); 32 | ffi::lua_getglobal(lua, c_ptr!("type")); 33 | ffi::lua_getglobal(lua, c_ptr!("tostring")); 34 | for i in 1..=top { 35 | ffi::lua_pushvalue(lua, -2); 36 | ffi::lua_pushvalue(lua, i); 37 | assert_eq!(ffi::lua_pcall(lua, 1, 1, 0), 0); 38 | let t = std::ffi::CStr::from_ptr(ffi::lua_tostring(lua, -1)).to_owned(); 39 | let t = t.to_string_lossy(); 40 | ffi::lua_pop(lua, 1); 41 | 42 | ffi::lua_pushvalue(lua, -1); 43 | ffi::lua_pushvalue(lua, i); 44 | assert_eq!(ffi::lua_pcall(lua, 1, 1, 0), 0); 45 | let s = std::ffi::CStr::from_ptr(ffi::lua_tostring(lua, -1)).to_owned(); 46 | let s = s.to_string_lossy(); 47 | ffi::lua_pop(lua, 1); 48 | 49 | writeln!(out, "{}: {}({})", i, t, s)?; 50 | } 51 | ffi::lua_settop(lua, top); 52 | Ok(()) 53 | } 54 | 55 | #[allow(clippy::missing_safety_doc)] 56 | pub unsafe fn dump_stack_raw(lua: LuaState) { 57 | dump_stack_raw_to(lua, std::io::stderr()).unwrap() 58 | } 59 | -------------------------------------------------------------------------------- /tlua/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! implement_lua_push { 3 | ($ty:ty, $cb:expr) => { 4 | impl<'lua, L> $crate::Push for $ty 5 | where 6 | L: $crate::AsLua<'lua>, 7 | { 8 | type Err = $crate::Void; // TODO: use ! instead 9 | #[inline] 10 | fn push_to_lua(&self, lua: L) -> Result<$crate::PushGuard, ($crate::Void, L)> { 11 | Ok($crate::push_userdata(self, lua, $cb)) 12 | } 13 | } 14 | 15 | impl<'lua, L> $crate::PushOne for $ty where L: $crate::AsLua<'lua> {} 16 | }; 17 | } 18 | 19 | #[macro_export] 20 | macro_rules! implement_lua_read { 21 | ($ty:ty) => { 22 | impl<'s, 'c> tlua::LuaRead<&'c mut tlua::InsideCallback> for &'s mut $ty { 23 | #[inline] 24 | fn lua_read_at_position( 25 | lua: &'c mut tlua::InsideCallback, 26 | index: i32, 27 | ) -> Result<&'s mut $ty, &'c mut tlua::InsideCallback> { 28 | // FIXME: 29 | unsafe { ::std::mem::transmute($crate::read_userdata::<$ty>(lua, index)) } 30 | } 31 | } 32 | 33 | impl<'s, 'c> tlua::LuaRead<&'c mut tlua::InsideCallback> for &'s $ty { 34 | #[inline] 35 | fn lua_read_at_position( 36 | lua: &'c mut tlua::InsideCallback, 37 | index: i32, 38 | ) -> Result<&'s $ty, &'c mut tlua::InsideCallback> { 39 | // FIXME: 40 | unsafe { ::std::mem::transmute($crate::read_userdata::<$ty>(lua, index)) } 41 | } 42 | } 43 | 44 | impl<'s, 'b, 'c> tlua::LuaRead<&'b mut &'c mut tlua::InsideCallback> for &'s mut $ty { 45 | #[inline] 46 | fn lua_read_at_position( 47 | lua: &'b mut &'c mut tlua::InsideCallback, 48 | index: i32, 49 | ) -> Result<&'s mut $ty, &'b mut &'c mut tlua::InsideCallback> { 50 | let ptr_lua = lua as *mut &mut tlua::InsideCallback; 51 | let deref_lua = unsafe { ::std::ptr::read(ptr_lua) }; 52 | let res = Self::lua_read_at_position(deref_lua, index); 53 | match res { 54 | Ok(x) => Ok(x), 55 | _ => Err(lua), 56 | } 57 | } 58 | } 59 | 60 | impl<'s, 'b, 'c> tlua::LuaRead<&'b mut &'c mut tlua::InsideCallback> for &'s $ty { 61 | #[inline] 62 | fn lua_read_at_position( 63 | lua: &'b mut &'c mut tlua::InsideCallback, 64 | index: i32, 65 | ) -> Result<&'s $ty, &'b mut &'c mut tlua::InsideCallback> { 66 | let ptr_lua = lua as *mut &mut tlua::InsideCallback; 67 | let deref_lua = unsafe { ::std::ptr::read(ptr_lua) }; 68 | let res = Self::lua_read_at_position(deref_lua, index); 69 | match res { 70 | Ok(x) => Ok(x), 71 | _ => Err(lua), 72 | } 73 | } 74 | } 75 | }; 76 | } 77 | 78 | #[macro_export] 79 | macro_rules! c_str { 80 | ($s:literal) => {{ 81 | fn f(b: &[u8]) -> &::std::ffi::CStr { 82 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(b) } 83 | } 84 | f(::std::concat!($s, "\0").as_bytes()) 85 | }}; 86 | } 87 | 88 | #[macro_export] 89 | macro_rules! c_ptr { 90 | ($s:literal) => { 91 | $crate::c_str!($s).as_ptr().cast::<::std::os::raw::c_char>() 92 | }; 93 | } 94 | 95 | /// Throws the lua error with the given message. 96 | /// The first argument is the lua context in which the error should be thrown. 97 | /// When throwing an error from a rust callback use the lua state 98 | /// which was passed to the callback. 99 | /// 100 | /// This macro will exit the current function so no code after it will be executed. 101 | /// 102 | /// # Example 103 | /// ```no_run 104 | /// let lua = tlua::Lua::new(); 105 | /// lua.set("rust_callback_which_may_throw", 106 | /// tlua::Function::new(|arg1: i32, arg2: String, lua: tlua::LuaState| { 107 | /// // - `arg1` & `arg2` are passed by caller from lua 108 | /// // - `lua` is a special argument inserted by tlua. 109 | /// // Only it should be used with `tlua::error!()`! 110 | /// tlua::error!(lua, "invalid arguments: {arg1}, {arg2}"); 111 | /// })); 112 | /// ``` 113 | #[macro_export] 114 | macro_rules! error { 115 | ($l:expr, $($args:tt)+) => {{ 116 | let msg = ::std::format!($($args)+); 117 | // Bind the metavariable outside unsafe block to prevent users 118 | // accidentally doing unsafe things 119 | let l = &$l; 120 | #[allow(unused_unsafe)] 121 | unsafe { 122 | let lua = $crate::AsLua::as_lua(l); 123 | $crate::ffi::lua_pushlstring(lua, msg.as_ptr() as _, msg.len()); 124 | $crate::ffi::lua_error($crate::AsLua::as_lua(l)); 125 | } 126 | unreachable!("luaL_error never returns") 127 | }}; 128 | } 129 | 130 | #[macro_export] 131 | macro_rules! unwrap_or { 132 | ($o:expr, $($else:tt)+) => { 133 | if let Some(v) = $o { 134 | v 135 | } else { 136 | $($else)+ 137 | } 138 | } 139 | } 140 | 141 | #[macro_export] 142 | macro_rules! unwrap_ok_or { 143 | ($r:expr, $err:pat => $($else:tt)+) => { 144 | match $r { 145 | Ok(v) => v, 146 | $err => $($else)+, 147 | } 148 | } 149 | } 150 | 151 | #[macro_export] 152 | macro_rules! nzi32 { 153 | ($i:expr) => { 154 | #[allow(unused_unsafe)] 155 | { 156 | const _: () = assert!($i != 0, "NonZeroI32 cannot be equal to 0"); 157 | unsafe { ::std::num::NonZeroI32::new_unchecked($i) } 158 | } 159 | }; 160 | } 161 | -------------------------------------------------------------------------------- /tlua/src/test.rs: -------------------------------------------------------------------------------- 1 | //! Internals used by custom test runtime to run tests that require tarantool environment 2 | use tester::{ShouldPanic, TestDesc, TestDescAndFn, TestFn, TestName, TestType}; 3 | 4 | #[derive(Clone)] 5 | pub struct TestCase { 6 | pub name: &'static str, 7 | pub f: fn(), 8 | } 9 | 10 | // Linkme distributed_slice exports a symbol with the given name, so we must 11 | // make sure the name is unique, so as not to conflict with distributed slices 12 | // from other crates. 13 | #[::linkme::distributed_slice] 14 | pub static TLUA_TESTS: [TestCase] = [..]; 15 | 16 | pub fn collect() -> Vec { 17 | TLUA_TESTS 18 | .iter() 19 | .map(|case| TestDescAndFn { 20 | desc: TestDesc { 21 | name: TestName::StaticTestName(case.name), 22 | ignore: false, 23 | should_panic: ShouldPanic::No, 24 | allow_fail: false, 25 | test_type: TestType::IntegrationTest, 26 | }, 27 | testfn: TestFn::StaticTestFn(case.f), 28 | }) 29 | .collect() 30 | } 31 | -------------------------------------------------------------------------------- /tlua/src/util.rs: -------------------------------------------------------------------------------- 1 | pub fn hash(s: &str) -> u32 { 2 | // https://github.com/LuaJIT/LuaJIT/blob/1d7b5029c5ba36870d25c67524034d452b761d27/src/lj_str.c#L76 3 | let mut h = s.len() as u32; 4 | let mut a: u32; 5 | let mut b: u32; 6 | let len = s.len(); 7 | let s = s.as_ptr(); 8 | unsafe { 9 | match len { 10 | 0 => return 0, 11 | 1..=3 => { 12 | a = *s as _; 13 | h ^= *s.add(len - 1) as u32; 14 | b = *s.add(len >> 1) as _; 15 | h ^= b; 16 | h = h.wrapping_sub(b.rotate_left(14)); 17 | } 18 | _ => { 19 | a = s.cast::().read_unaligned(); 20 | h ^= s.add(len - 4).cast::().read_unaligned(); 21 | b = s 22 | .add((len >> 1).wrapping_sub(2)) 23 | .cast::() 24 | .read_unaligned(); 25 | h ^= b; 26 | h = h.wrapping_sub(b.rotate_left(14)); 27 | b += s 28 | .add((len >> 2).wrapping_sub(1)) 29 | .cast::() 30 | .read_unaligned(); 31 | } 32 | } 33 | } 34 | a ^= h; 35 | a = a.wrapping_sub(h.rotate_left(11)); 36 | b ^= a; 37 | b = b.wrapping_sub(a.rotate_left(25)); 38 | h ^= b; 39 | h = h.wrapping_sub(b.rotate_left(16)); 40 | h 41 | } 42 | --------------------------------------------------------------------------------