18 个版本 (11 个重大更新)
0.11.0 | 2024年7月28日 |
---|---|
0.10.0 | 2024年2月26日 |
0.9.0 | 2023年11月15日 |
0.8.0 | 2023年4月6日 |
0.0.1 |
|
#192 在 数据库接口 中
每月416 次下载
用于 6 个Crates (直接使用5个)
30KB
330 行
Turbosql
一个简单的本地数据持久层,由SQLite支持。
- 由您的Rust
struct
自动定义模式 - 自动模式迁移
- 超级简单的基本
INSERT
/SELECT
/UPDATE
/DELETE
操作 - 如果需要,可以使用复杂的SQL
- 在编译时验证所有SQL(包括用户提供的SQL)
使用方法
use turbosql::{Turbosql, select, execute};
#[derive(Turbosql, Default)]
struct Person {
rowid: Option<i64>, // rowid member required & enforced at compile time
name: Option<String>,
age: Option<i64>,
image_jpg: Option<Vec<u8>>
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let name = "Joe";
// INSERT a row
let rowid = Person {
name: Some(name.to_string()),
age: Some(42),
..Default::default()
}.insert()?;
// SELECT all rows
let people = select!(Vec<Person>)?;
// SELECT multiple rows with a predicate
let people = select!(Vec<Person> "WHERE age > " 21)?;
// SELECT a single row with a predicate
let mut person = select!(Person "WHERE name = " name)?;
// UPDATE based on rowid, rewrites all fields in database row
person.age = Some(43);
person.update()?;
// UPDATE with manual SQL
execute!("UPDATE person SET age = " 44 " WHERE name = " name)?;
// DELETE
execute!("DELETE FROM person WHERE rowid = " 1)?;
Ok(())
}
请参阅integration_test.rs
或 trevyn/turbo 以获取更多使用示例!
内部结构
Turbosql为每个结构体生成SQLite模式和预编译查询
use turbosql::Turbosql;
#[derive(Turbosql, Default)]
struct Person {
rowid: Option<i64>, // rowid member required & enforced
name: Option<String>,
age: Option<i64>,
image_jpg: Option<Vec<u8>>
}
↓ 自动生成并验证模式
CREATE TABLE person (
rowid INTEGER PRIMARY KEY,
name TEXT,
age INTEGER,
image_jpg BLOB,
) STRICT
INSERT INTO person (rowid, name, age, image_jpg) VALUES (?, ?, ?, ?)
SELECT rowid, name, age, image_jpg FROM person
带有SQL谓词的查询也在编译时组装和验证。请注意,参数绑定时SQL类型和Rust类型目前不在编译时进行检查。
let people = select!(Vec<Person> "WHERE age > ?", 21);
↓
SELECT rowid, name, age, image_jpg FROM person WHERE age > ?
自动模式迁移
在编译时,#[derive(Turbosql)]
宏运行并在您的项目根目录中创建一个 migrations.toml
文件,该文件描述了数据库模式。
每次您更改 struct
声明并重新运行宏(例如,通过 cargo
或 rust-analyzer
),都会生成更新数据库模式的迁移SQL语句。这些新语句记录在 migrations.toml
中,并自动嵌入到您的二进制文件中。
#[derive(turbosql::Turbosql, Default)]
struct Person {
rowid: Option<i64>,
name: Option<String>
}
↓ 自动生成 migrations.toml
migrations_append_only = [
'CREATE TABLE person(rowid INTEGER PRIMARY KEY) STRICT',
'ALTER TABLE person ADD COLUMN name TEXT',
]
output_generated_schema_for_your_information_do_not_edit = '''
CREATE TABLE person (
rowid INTEGER PRIMARY KEY,
name TEXT
) STRICT
'''
当您的模式发生变化时,任何新的二进制版本都会自动将旧的数据库文件迁移到当前模式,通过按顺序应用适当的迁移。
此迁移过程是一个单向的齿轮:旧版本的二进制运行在具有较新模式的数据库文件上时,将检测到模式不匹配,并阻止操作在未来模式的数据库文件上。
在开发过程中创建的未使用或回滚的迁移可以在发布前从 migrations.toml
中手动删除,但已应用这些已删除迁移的任何数据库文件将出错,必须重新构建。请谨慎操作。如有疑问,请勿手动编辑 migrations.toml
,一切应该都能正常工作。
- 只需声明并自由地向您的
struct
中添加字段。 - 查看项目根目录中生成的
migrations.toml
文件以了解情况。 - 如果您遇到任何奇怪的编译器错误,请先尝试重新编译;根据 proc 宏的运行顺序,有时在模式更改后只需要一点推动即可同步。
- 模式迁移是单向的,只可附加。这与 leafac/sqlite-migration 对 Node.js 生态系统采用的方法类似;请参阅该项目以了解优点的讨论!
- 启动时,使用较新模式构建的二进制版本将自动将适当的迁移应用到较旧的数据库。
- 如果您想尝试冒险,可以将您自己的模式迁移条目添加到列表底部。(例如创建索引等。)
- 您还可以手动编写复杂的迁移,请参阅 turbo/migrations.toml 以获取一些示例。
- 如有任何问题或建议,请打开 GitHub 问题!
我的数据在哪里?
SQLite 数据库文件位于由 directories_next::ProjectDirs::data_dir()
返回的目录中 + 您的可执行文件的名称,解析为类似
Linux |
|
macOS |
|
Windows |
|
事务和 async
SQLite以及许多通用文件系统仅提供阻塞(同步)API。在使用Rust的async
生态系统中的阻塞API时,正确的方法是使用您的执行器在预期阻塞的线程池上运行闭包的功能。例如:
#[derive(turbosql::Turbosql, Default)]
struct Person {
rowid: Option<i64>,
name: Option<String>
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let person = tokio::task::spawn_blocking(|| {
turbosql::select!(Option<Person> "WHERE name = ?", "Joe")
}).await??;
Ok(())
}
(请注意,spawn_blocking
返回一个JoinHandle
,它本身必须被解包,因此在这些示例的末尾需要??
。)
在底层,Turbosql使用持久的thread_local
数据库连接,因此来自同一线程的数据库调用连续序列将保证使用相同的专用数据库连接。因此,async
事务可以按此方式执行
use turbosql::{Turbosql, select, execute};
#[derive(Turbosql, Default)]
struct Person {
rowid: Option<i64>,
age: Option<i64>
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tokio::task::spawn_blocking(|| -> Result<(), turbosql::Error> {
Person { rowid: None, age: Some(21) }.insert()?;
execute!("BEGIN IMMEDIATE TRANSACTION")?;
let p = select!(Person "WHERE rowid = ?", 1)?;
// [ ...do any other blocking things... ]
execute!(
"UPDATE person SET age = ? WHERE rowid = ?",
p.age.unwrap_or_default() + 1,
1
)?;
execute!("COMMIT")?;
Ok(())
}).await??;
Ok(())
}
Turbosql将SQLite的busy_timeout
设置为3秒,因此任何表锁竞争都会在该时间段内自动重试,之后无法获取锁的命令将返回错误。
有关Turbosql对async
和事务的方法的进一步讨论,请参阅https://github.com/trevyn/turbosql/issues/4。非常欢迎对解决方案的易用性进行改进的想法。
-wal
和-shm
文件
SQLite是一个非常可靠的数据库引擎,但了解它与文件系统的接口总是有帮助。主要的.sqlite
文件包含数据库的大部分内容。在数据库写入期间,SQLite还创建了.sqlite-wal
和.sqlite-shm
文件。如果主进程在没有刷新写入的情况下终止,您可能会得到这三个文件,而您期望只有一个文件。这总是没有问题的;在下次启动时,SQLite知道如何解决任何中断的写入并理解世界。然而,如果存在-wal
和/或-shm
文件,它们被认为是数据库完整性的关键。删除它们可能导致数据库损坏。请参阅https://sqlite.ac.cn/tempfiles.html。
查询示例形式
查看integration_test.rs
以获取CI中工作并已测试的更多示例。
原始类型 |
返回一个转换为指定类型的值,如果没有可用行,则返回
|
Vec<_> |
返回包含另一种类型的 |
Option<_> |
如果没有行,则返回 |
您的结构体 |
如果类型是
您还可以使用其他结构体类型;列名必须与结构体匹配,并且您必须在SQL中指定源表。
有时一切都是可选的;此示例将检索所有 |
"turbosql" 还是 "Turbosql"?
由您选择,但您绝对不要在名称中的其他字母大写! ;)
许可协议:MIT OR Apache-2.0 OR CC0-1.0(公有领域)
依赖项
~27MB
~508K SLoC