#sql #macro

include-sql-helper

include-sql helper。提供API的非宏项。

1 个不稳定版本

0.1.0 2019年3月13日

#2811数据库接口

MIT 许可协议

3KB

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配套crate,如 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块声明和使用语句批处理。注意,当语句是文件中的最后一个语句,或者其后跟有另一个语句,该语句的头部将自动结束前一个语句时,语句终止符是可选的。

无运行时依赖