#sql #macro

include-sql

A Yesql启发的宏,用于在Rust中使用SQL

9个版本

0.3.1 2024年3月11日
0.3.0 2023年2月10日
0.2.5 2022年4月22日
0.2.3 2022年2月22日
0.1.0 2019年3月13日

#2630 in 数据库接口


3 crates 中使用

MIT 许可证

53KB
997

crates.io Documentation MIT

include-sql 是一个用于在Rust中 使用 SQL的宏。

include-sql 受到 Yesql 的启发。它允许程序员以SQL编写SQL查询,将它们与Rust代码分开,并通过此库提供的proc-macro轻松地将它们嵌入到Rust程序中。

仅凭自身,include-sql实际上做得很少 - 它读取并解析SQL文件,将其转换为对 impl_sql 宏的调用。预计 impl_sql 将由使用include-sql的项目或外部库提供。例如,有几个include-sql配套crates,如 include-postgres-sqlinclude-sqlite-sqlinclude-oracle-sql,它们实现了 impl_sql。如果它们的SQL嵌入方法被认为适当且方便,则可以直接使用。或者,它们可以用作实现您自己的 impl_sql 的起点。

示例

由于include-sql不打算直接使用,我们将使用 include-sqlite-sql 来说明工作流程。

include-sqlite-sql 作为依赖项添加

[dependencies]
include-sqlite-sql = "0.2"

编写您的SQL并将其保存到文件中。例如,以下内容将保存为项目 sql 文件夹中的 library.sql

-- name: get_loaned_books?
-- Returns the list of books loaned to a patron
-- # Parameters
-- param: user_id: &str - user ID
SELECT book_title
  FROM library
 WHERE loaned_to = :user_id
 ORDER BY 1
/
-- name: loan_books!
-- Updates the book record to reflect loan to a patron
-- # Parameters
-- param: book_titles: &str - book titles
-- param: user_id: &str - user ID
UPDATE library
   SET loaned_to = :user_id
     , loaned_on = current_timestamp
 WHERE book_title IN (:book_titles)
/

注意 参数顺序由 param 声明定义。

然后在Rust中使用它

use include_sqlite_sql::{include_sql, impl_sql};
use rusqlite::{Result, Connection};

include_sql!("sql/library.sql");

fn main() -> Result<()> {
    let db = Connection::open("library.db")?;

    db.loan_books(&["Where the Sidewalk Ends", "A Wrinkle in Time", "Dune"], "Penny Teller")?;

    db.get_loaned_books("Leonard Hofstadter", |row| {
        let book_title : &str = row.get_ref("book_title")?.as_str()?;
        println!("{book_title}");
        Ok(())
    })?;

    Ok(())
}

注意 SQL文件的路径必须是相对于项目根目录的,即相对于 CARGO_MANIFEST_DIR,即使您将SQL文件与包含它的rust模块放在同一目录下。因为include-sql针对稳定版Rust,此要求将持续到 SourceFile 稳定。

内部机制

在解析和验证SQL文件的 include-sql 内容后,将生成以下调用

impl_sql!{ LibrarySql =
  {
    ? get_loaned_books (: user_id (&str))
    " Returns the list of books loaned to a patron\n # Parameters\n * `user_id` - user ID"
    $ "SELECT book_title\n  FROM library\n WHERE loaned_to = " :user_id "\n ORDER BY 1"
  },
  {
    ! loan_books (# book_titles (&str) : user_id (&str))
    " Updates the book records to reflect loan to a patron\n # Parameters\n * `user_id` - user ID\n * `book_titles` - book titles"
    $ "UPDATE library\n   SET loaned_to = " : user_id "\n,     loaned_on = current_timestamp\n WHERE book_title IN (" # book_titles ")"
  }
}

它将 include_sqlite_sql::impl_sql 转换为以下实现

trait LibrarySql {
    /// Returns the list of books loaned to a patron
    /// # Parameters
    /// * `user_id` - user ID
    fn get_loaned_books<F>(&self, user_id: &str, row_callback: F) -> rusqlite::Result<()>
    where F: FnMut(&rusqlite::Row) -> rusqlite::Result<()>;

    /// Updates the book records to reflect loan to a patron
    /// # Parameters
    /// * `book_titles` - book titles
    /// * `user_id` - user ID
    fn loan_books(&self, book_ids: &[&str], user_id: &str) -> rusqlite::Result<usize>;
}

当然,它还实现了这个特性

impl LibrarySql for rusqlite::Connection {
    /// ...
}

文档

包含的文档描述了支持的SQL文件格式,并提供了编写自己的impl_sql宏的说明。

💥 0.3版本中的重大更改

  • 生成的方法的参数顺序由param描述符的顺序定义。这是一个潜在的破坏性更改,因为之前生成的方法的参数顺序遵循SQL语句中参数的顺序。当SQL语句头部不使用param描述符时,则生成的泛型方法参数将根据它们在SQL语句中的出现顺序进行排序。
  • 语句以斜杠/结尾,而不是分号;。这是为了允许声明和使用SQLite的语句批次以及Oracle的PL/SQL块。请注意,当语句是文件中的最后一个语句或其后跟有另一个语句时,语句终止符是可选的,因为头部将自动终止前面的语句。

依赖项

~3.5–4.5MB
~88K SLoC