5个版本
0.2.1 | 2024年8月7日 |
---|---|
0.2.0 | 2023年5月29日 |
0.1.2 | 2022年8月8日 |
0.1.1 | 2022年3月17日 |
0.1.0 | 2021年12月23日 |
#18 在 #tarantool
3,854 每月下载量
在 10 个crate中使用(通过 tlua)
31KB
766 行
Tarantool Rust SDK
Tarantool Rust SDK为Rust应用程序提供与Tarantool交互的库。本文档描述了Rust的Tarantool API绑定,包括以下API:
- Box:spaces、indexes、sequences
- Fibers:fiber属性、条件变量、latches、异步运行时
- CoIO
- 事务
- 模式管理
- 协议实现(
net.box
):CRUD、存储过程调用、触发器 - Tuple实用工具
- 日志(见 https://docs.rs/log/)
- 错误处理
链接
另请参阅
入门
以下说明将帮助您在本地机器上获取项目的副本并运行。有关部署,请参阅文件末尾的部署说明。
先决条件
- Tarantool 2.10+
macOS中的链接问题
在macOS上,您可能会遇到以下链接错误: ld: symbol(s) not found for architecture x86_64
。要解决此问题,请将以下行放入您的 $CARGO_HOME/config.toml
(默认为 ~/.cargo/config.toml
)。
[target.x86_64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"
]
用法
将以下行添加到您的项目Cargo.toml中
[dependencies]
tarantool = "5.0"
[lib]
crate-type = ["cdylib"]
请参阅https://github.com/picodata/brod获取示例用法。
功能
net_box
- 启用协议实现(默认启用)schema
- 启用模式操作工具(目前仍在开发中)
存储过程
有多种方式可以让 Tarantool 调用 Rust 代码。它可以使用插件、Lua 到 Rust FFI 代码生成器或存储过程。在此文件中,我们只介绍第三种选项,即 Rust 存储过程。尽管 Tarantool 总是将 Rust 程序视为 "C 函数",但我们继续使用 "存储过程" 这个术语作为约定,也有历史原因。
本教程包含以下简单步骤
examples/easy
- 打印 "hello world";examples/harder
- 解码传入的参数值;examples/hardest
- 使用此库进行 DBMS 插入;examples/read
- 使用此库进行 DBMS 查询;examples/write
- 使用此库进行 DBMS 替换。
我们的示例是用户自信地开始编写自己的存储过程的好起点。
创建 Cargo 项目
安装先决条件后,按照以下步骤操作
- 创建 Cargo 项目
$ cargo init --lib
- 将以下行添加到
Cargo.toml
[package]
name = "easy"
version = "0.1.0"
edition = "2018"
# author, license, etc
[dependencies]
tarantool = "5.0"
serde = "1.0"
[lib]
crate-type = ["cdylib"]
- 使用以下脚本创建名为
init.lua
的服务器入口点
box.cfg({listen = 3301})
box.schema.func.create('easy', {language = 'C', if_not_exists = true})
box.schema.func.create('easy.easy2', {language = 'C', if_not_exists = true})
box.schema.user.grant('guest', 'execute', 'function', 'easy', {if_not_exists = true})
box.schema.user.grant('guest', 'execute', 'function', 'easy.easy2', {if_not_exists = true})
要了解更多关于上述命令的信息,请查阅 Tarantool 文档中的语法和使用细节
- 编辑
lib.rs
文件并添加以下行
use tarantool::proc;
#[proc]
fn easy() {
println!("hello world");
}
#[proc]
fn easy2() {
println!("hello world -- easy2");
}
我们现在准备好提供一些使用示例。我们将展示三种难度级别的函数调用,从基本用法示例(easy
),到一些更复杂的共享库示例(harder
和 hardest
)。此外,还将提供读取和写入数据的单独示例。
基本用法示例
编译应用程序并启动服务器
$ cargo build
$ LUA_CPATH=target/debug/lib?.so tarantool init.lua
请确保生成的 .so
文件位于之前指定的 LUA_CPATH
上,以便以下行正常工作。
尽管 Rust 和 Lua 布局约定不同,但我们可以利用 Lua 的灵活性,并通过显式设置如上所示的 LUA_CPATH 环境变量来修复它。
现在您可以发送请求。打开单独的控制台窗口,并以客户端方式运行 Tarantool。将以下内容粘贴到控制台中
conn = require('net.box').connect(3301)
conn:call('easy')
如有必要,请查阅 net.box 模块文档。
上面的代码建立了一个服务器连接并调用了 'easy' 函数。由于 lib.rs
中的 easy()
函数以 println!("hello world")
开头,因此在服务器控制台输出中会出现 "hello world" 字符串。
代码还会检查调用是否成功。由于 easy()
函数在 lib.rs
中以返回 0 结束,因此没有错误信息要显示,请求已经结束。
现在让我们在 lib.rs 中调用另一个函数,即 easy2()
。这个序列几乎与 easy()
函数相同,但有一个区别:如果文件名不匹配函数名,我们必须明确指定 {file-name}.{function-name}。
conn:call('easy.easy2')
...这次的结果将是 hello world -- easy2
。
如您所见,调用 Rust 函数非常简单。
检索调用参数
创建一个新的名为 "harder" 的crate。将这些行放入 lib.rs
#[tarantool::proc]
fn harder(fields: Vec<i32>) {
println!("field_count = {}", fields.len());
for val in fields {
println!("val={}", val);
}
}
上面的代码定义了一个存储过程,该过程接受一系列整数。
使用 cargo build
将程序编译成 harder.so
库。
现在回到客户端并执行这些请求
box.schema.func.create('harder', {language = 'C'})
box.schema.user.grant('guest', 'execute', 'function', 'harder')
passable_table = {}
table.insert(passable_table, 1)
table.insert(passable_table, 2)
table.insert(passable_table, 3)
capi_connection:call('harder', {passable_table})
这次调用将 Lua 表格 (passable_table
) 传递给 harder()
函数。 harder()
函数将检测它,正如我们在上面的示例中编码的 args
部分。
控制台输出现在应该看起来像这样
tarantool> capi_connection:call('harder', {passable_table})
field_count = 3
val=1
val=2
val=3
---
- []
...
如您所见,解码传递给 Rust 函数的参数值可能很棘手,因为它需要编码额外的例程。
访问 Tarantool 空间
创建一个新的 crate,名为 "hardest"。将这些行放入 lib.rs
use serde::{Deserialize, Serialize};
use tarantool::{
proc,
space::Space,
tuple::{Encode, Tuple},
};
#[derive(Serialize, Deserialize)]
struct Row {
pub int_field: i32,
pub str_field: String,
}
impl Encode for Row {}
#[proc]
fn hardest() -> Tuple {
let mut space = Space::find("capi_test").unwrap();
let result = space.insert(&Row {
int_field: 10000,
str_field: "String 2".to_string(),
});
result.unwrap()
}
这次 Rust 函数执行以下操作
- 通过调用
Space::find()
方法找到capi_test
空间; - 将行结构序列化为自动模式下的元组;
- 使用
.insert()
插入元组。
使用 cargo build
将程序编译成 hardest.so
库。
现在回到客户端并执行这些请求
box.schema.func.create('hardest', {language = "C"})
box.schema.user.grant('guest', 'execute', 'function', 'hardest')
box.schema.user.grant('guest', 'read,write', 'space', 'capi_test')
capi_connection:call('hardest')
此外,在客户端执行另一个请求
box.space.capi_test:select()
结果应该看起来像这样
tarantool> box.space.capi_test:select()
---
- - [10000, 'String 2']
...
上述证明 hardest()
函数已成功。
读取示例
创建一个新的 crate "read"。将这些行放入 lib.rs
use serde::{Deserialize, Serialize};
use tarantool::{
proc,
space::Space,
tuple::Encode,
};
#[derive(Serialize, Deserialize, Debug)]
struct Row {
pub int_field: i32,
pub str_field: String,
}
impl Encode for Row {}
#[proc]
fn read() {
let space = Space::find("capi_test").unwrap();
let key = 10000;
let result = space.get(&(key,)).unwrap();
assert!(result.is_some());
let result = result.unwrap().decode::<Row>().unwrap();
println!("value={:?}", result);
}
上面的代码执行以下操作
- 通过调用
Space::find()
找到capi_test
空间; - 使用 Rust 元组字面量格式化搜索键 = 10000(这是序列化结构的替代方法);
- 使用
.get()
获取元组; - 反序列化结果。
使用 cargo build
将程序编译成 read.so
库。
现在回到客户端并执行这些请求
box.schema.func.create('read', {language = "C"})
box.schema.user.grant('guest', 'execute', 'function', 'read')
box.schema.user.grant('guest', 'read,write', 'space', 'capi_test')
capi_connection:call('read')
capi_connection:call('read')
的结果应该看起来像这样
tarantool> capi_connection:call('read')
uint value=10000.
string value=String 2.
---
- []
...
以上证明read()
函数已成功执行。
编写示例
创建一个名为"write"的新crate。将这些行放入lib.rs
use tarantool::{
proc,
error::Error,
fiber::sleep,
space::Space,
transaction::transaction,
};
#[proc]
fn write() -> Result<(i32, String), String> {
let mut space = Space::find("capi_test")
.ok_or_else(|| "Can't find space capi_test".to_string())?;
let row = (1, "22".to_string());
transaction(|| -> Result<(), Error> {
space.replace(&row)?;
Ok(())
})
.unwrap();
sleep(std::time::Duration::from_millis(1));
Ok(row)
}
上面的代码执行以下操作
- 通过调用
Space::find()
找到capi_test
空间; - 准备行值;
- 启动事务;
- 替换
box.space.capi_test
中的元组; - 完成事务
- 在闭包上接收到
Ok()
时执行提交; - 在接收到
Error()
时执行回滚;
- 在闭包上接收到
- 将整个元组返回给调用者,并让调用者显示它。
使用cargo build
将程序编译成write.so
库。
现在回到客户端并执行这些请求
box.schema.func.create('write', {language = "C"})
box.schema.user.grant('guest', 'execute', 'function', 'write')
box.schema.user.grant('guest', 'read,write', 'space', 'capi_test')
capi_connection:call('write')
capi_connection:call('write')
的结果应该看起来像这样
tarantool> capi_connection:call('write')
---
- [[1, '22']]
...
以上证明write()
函数已成功执行。
如你所见,Rust "存储过程"可以完全访问数据库。
清理
- 使用
box.schema.func.drop
删除每个函数元组。 - 使用
box.schema.capi_test:drop()
删除capi_test
空间。 - 删除为这篇教程创建的
*.so
文件。
运行测试
要调用自动测试,运行
make
make test
查看测试说明以获取有关测试结构和添加测试的更多信息。
贡献
欢迎提交拉取请求。对于重大更改,请先提出一个问题来讨论您希望更改的内容。
请确保根据需要更新测试。
版本控制
我们使用SemVer进行版本控制。有关可用版本,请参阅此存储库的标签。
作者
- Anton Melnikov
- Dmitriy Koltsov
- Georgy Moshkin
- Egor Ivkov
© 2020-2022 Picodata.io https://git.picodata.io/picodata
许可证
本项目受BSD许可证的许可 - 有关详细信息,请参阅LICENSE文件。
依赖项
~1.5MB
~35K SLoC