13 个版本
0.2.7 | 2022 年 3 月 18 日 |
---|---|
0.2.6 | 2022 年 3 月 18 日 |
0.2.4 | 2022 年 2 月 9 日 |
0.2.1 | 2022 年 1 月 31 日 |
0.1.4 | 2022 年 1 月 29 日 |
#2103 在 数据库接口
30 每月下载量
用于 fnsql-macro
21KB
263 行
该 fnsql
crate 提供了对 SQL 查询的简单类型安全可选包装。您无需调用无类型的 .query()
和 .execute()
,而是调用自动生成的唯一包装器,这些包装器具有强类型,.query_<name>()
和 .execute_<name>()
。但是,您必须手动指定查询的输入和输出类型,但只需一次,与使用查询的代码分开。
这是一个非常简单的实现,它不会强迫任何架构或 ORM 强加于您,因此如果您已经使用 rusqlite
或 postgres
crates,您可以逐步用类型丰富的包装器替换无类型的查询,或者从具有偏见的 ORM 迁移。
生成这些包装器的方法是,为每个查询指定输入和输出类型。例如,考虑以下基于 rusqlite
的示例中指定的 fnsql
定义
fnsql::fnsql! {
#[rusqlite, test]
create_table_pet() {
"CREATE TABLE pet (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
data BLOB
)"
}
#[rusqlite, test(with=[create_table_pet])]
insert_new_pet(name: String, data: Option<Vec<u8>>) {
"INSERT INTO pet (name, data) VALUES (:name, :data)"
}
#[rusqlite, test(with=[create_table_pet])]
get_pet_id_data(name: Option<String>) -> [(i32, Option<Vec<u8>>, String)] {
"SELECT id, data, name FROM pet WHERE pet.name = :name"
}
}
这些定义可以这样使用(注释的是之前无类型接口的使用方式)
let mut conn = rusqlite::Connection::open_in_memory()?;
conn.execute_create_table_pet()?;
// conn.execute(
// "CREATE TABLE pet (
// id INTEGER PRIMARY KEY,
// name TEXT NOT NULL,
// data BLOB
// )",
// [],
// )?;
conn.execute_insert_new_pet(&me.name, &me.data)?;
// conn.execute(
// "INSERT INTO pet (name, data) VALUES (?1, ?2)",
// params![me.name, me.data],
// )?;
let mut stmt = conn.prepare_get_pet_id_data()?;
// let mut stmt = conn.prepare("SELECT id, data, name FROM pet WHERE pet.name = :name")?;
let pet_iter = stmt.query_map(&Some("Max".to_string()), |id, data, name| {
Ok::<_, rusqlite::Error>(Pet {
id,
data,
name,
})
})?;
// let pet_iter = stmt.query_map([(":name", "Max".to_string())], |row| {
// Ok(Pet {
// id: row.get(0)?,
// name: row.get(1)?,
// data: row.get(2)?,
// })
// })?;
技术讨论
这个 crate 的想法是允许直接使用 SQL,但从不使用内联查询或在调用位置进行类型推断。相反,我们声明每个查询在顶层,为每个查询提供一个名称和从该名称派生的访问器方法。
- 命名变量的类型以 Rust 类似语法给出。
- 返回行的类型也提供了。
fnsql
不保证类型与查询匹配,您将使用cargo test
和没有额外的代码来发现它。fnsql
为每个查询编写测试。-Arbitrary
用于生成参数值。- 如果测试一个查询依赖于另一个,您可以使用
test(with=[..])
来指定。
running 3 tests
test auto_create_table_pet ... ok
test auto_insert_new_pet ... ok
test auto_get_pet_id_data ... ok
以下是为了允许生成的查询测试编译
[dev-dependencies]
arbitrary = { version = "1", features = ["derive"] }
限制
- 尽管它确实提供了自动生成的测试来验证
cargo test
中的查询,但它并没有基于 SQL 查询字符串进行任何编译时验证。 - 目前仅支持
rusqlite
和postgres
。
依赖关系
~3–14MB
~167K SLoC