#sql-query #proc-macro #sql

fnsql

类型安全的 SQL 查询包装器

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

MIT/Apache

21KB
263

fnsql crate 提供了对 SQL 查询的简单类型安全可选包装。您无需调用无类型的 .query().execute(),而是调用自动生成的唯一包装器,这些包装器具有强类型,.query_<name>().execute_<name>()。但是,您必须手动指定查询的输入和输出类型,但只需一次,与使用查询的代码分开。

这是一个非常简单的实现,它不会强迫任何架构或 ORM 强加于您,因此如果您已经使用 rusqlitepostgres 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 查询字符串进行任何编译时验证。
  • 目前仅支持 rusqlitepostgres

依赖关系

~3–14MB
~167K SLoC