#rusqlite #sqlite #database #proc-macro

exemplar_proc_macro

为 exemplar crate 提供过程宏

12 个不稳定版本 (3 个破坏性更新)

0.7.1 2023 年 10 月 25 日
0.7.0 2023 年 10 月 16 日
0.6.3 2023 年 10 月 16 日
0.5.7 2023 年 10 月 12 日
0.0.0 2023 年 10 月 9 日

#1270 in 过程宏

每月 36 次下载
用于 exemplar

MIT/Apache

17KB
433

Exemplar

rusqlite 的样板代码消除器。

入门指南

体验一下您可以做什么

#[derive(Debug, PartialEq, Model)]
#[table("users")]
#[check("../tests/schema.sql")]
struct User {
   username: String,
   #[bind(bind_path)]
   #[extr(extr_path)]
   home_dir: PathBuf,
   #[column("pwd")]
   password: Vec<u8>,
}
 
fn main() -> Result<()> {
    let conn = Connection::open_in_memory()?;
 
    conn.execute_batch(
        include_str!("../tests/schema.sql")
    )?;
 
    let alice = User {
        username: "Alice".to_owned(),
        home_dir: "/var/home/alice".into(),
        password: b"hunter2".to_vec()
    };
 
    let bob = User {
        username: "Bob".to_owned(),
        home_dir: "/var/home/robert".into(),
        password: b"password".to_vec()
    };
 
    alice.insert(&conn)?;
    bob.insert(&conn)?;
 
    let mut stmt = conn.prepare("
        SELECT * FROM users ORDER BY username ASC
    ")?;
    
    let mut iter = stmt.query_and_then([], User::from_row)?;
 
    assert_eq!(alice, iter.next().unwrap()?);
    assert_eq!(bob, iter.next().unwrap()?);
 
    Ok(())
}

Exemplar 基于具有自己的 Model trait,该 trait 有自己的 derive 宏

  • 查看上述 的文档以开始。
  • 对于在模型中处理 enum,请查看 sql_enum 宏。
  • 对于处理 "匿名" 记录类型,请查看 record 宏。

特性

  • 与原始 SQL 一起工作,而不是与之对抗。
  • 轻量级、零成本 API。
    • Exemplar 的大部分功能都围绕 Model trait 展开,该 trait 在运行前被内联和单态化。生成的代码大致相当于您使用纯 rusqlite 编写的代码。
    • 设计为即插即用;尽可能重用 rusqlite 的现有类型,包括其 Result 类型别名。
    • 支持任何可以 Derefrusqlite::Connection 的类型,例如事务或连接池。
  • 可选的测试推导,以防止数据库模式与 Rust 模型类型之间的漂移。
  • 用于处理与 SQL 兼容的 enum 和映射到临时查询的 "匿名" 记录类型的宏。
  • 在运行时对 dyn Model 进行反射/操作的一些能力。

如果您只需要使用 sqlite 对 Rust 数据进行 CRUD 操作,而不需要完整的 ORM 或企业级 DBMS,那么 Exemplar 就是您所需要的!

常见问题解答

"Exemplar 都不做些什么?"

一些关键特性

  • 模式生成和管理。Exemplar 明确不是一个 ORM,并且很难在不落入 ORM 领域的情况下表示外键和迁移等概念。
    • 如果您认为这是“必需”的,可以查看 dieselsqlx/seaorm,它们都支持 SQLite。
  • 查询生成(不包括 INSERT。)
  • 接口兼容性。仅支持 rusqlite

"它是不是非常快?"

是的。在我的机器上(根据 这些基准测试),Exemplar 可以

  • 在 ~600 纳秒内插入一个非平凡的模式类型(1.6百万行/秒)
  • 查询并重建同一类型在 ~9 微秒内(111,000 行/秒,使用 SELECT * LIMIT 1

显然,这种速度的功劳要归功于 SQLite 和 rusqlite 的开发者,但我可以自信地说,我没有拖慢速度!

"它与 serde-rusqlite 有何不同?"

serde_rusqlite 是一种巧妙的黑客手段,但它仍然涉及太多扭曲和样板代码,这不符合我的口味——这就是我创建 Exemplar 的原因。

我试图解决的问题点包括

  • 需要分配和操作一个 String 列表名称切片,以便有效地反序列化行——这可能是由于 serde 的限制?
    • Exemplar 静态知道期望哪些列,因此 from_row 不需要额外的输入,也不进行多余的分配。
  • 无字段 enum 的设计选择很奇怪——它们被低效地序列化为 TEXT 而不是 INTEGER。这对于调试来说很好,但我认为更快的选项应该是 Exemplar 的默认设置。
  • to_params_named(&row1).unwrap().to_slice().as_slice() 有点难以发音(尽管这可能是 serde 的怪异之处再次显现。)
    • 相当于 Exemplar 中的 row1.insert(&conn)row1.insert_with(&stmt)
  • 在编译和运行时都会出现一般 serde 的开销。
    • 基准测试表明,与 Exemplar 相比,serde_rusqlite 在插入操作上慢约 25%。
    • 检索操作同样快速,这可能是由于最终转换步骤与查询计算和 I/O 相比微不足道。

致谢

  • rusqlite,因为它为构建这个库提供了基础。
  • David Tolnay,感谢他提供的各种 proc macro 咒语 包。

依赖项

~310–770KB
~18K SLoC