include-sql 0.2.0

A Yesql inspired macro for using SQL in Rust
Documentation
**include-sql** is a macro for *using* SQL in Rust.

include-sql was inspired by [Yesql][1]. It allows the programmer to write SQL queries in SQL, keep them separate from the Rust code, and easily embed them into Rust programs via the proc-macro that this library provides.

All by itself include-sql actually does very little - it reads and parses SQL file and transforms it into a call to the `impl_sql` macro. It is expected that `impl_sql` is provided either by the project that uses include-sql or by an external library. For example, there are several include-sql companion crates, like [include-postgres-sql][2] and [include-sqlite-sql][3], that implement `impl_sql`. They can simply be used directly if their approaches to embedding SQL are deemed appropriate and convenient. Alternatively, they can be used as a starting point when implementing your own `impl_sql`.

# Example

As include-sql is not intended to be used directly, to illustrate the workflow we'll use [include-sqlite-sql][3].

Include `include-sqlite-sql` as a dependency:

```toml
[dependencies]
include-sqlite-sql = "0.1"
```

Write your SQL and save it in a file. For example, let's say the following is the content of the `library.sql` file that is saved in the project's `src` folder:

```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 records to reflect loan to a patron
-- # Parameters
-- param: user_id: &str - user ID
-- param: book_ids: u32 - book IDs
UPDATE library
   SET loaned_to = :user_id
     , loaned_on = current_timestamp
 WHERE book_id IN (:book_ids);
```

And then use it in Rust as:

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

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

fn main() -> Result<()> {
    let args : Vec<String> = std::env::args().collect();
    let dbpath = &args[1];
    let user_id = &args[2];

    let db = Connection::open(dbpath)?;

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

    Ok(())
}
```

> **Note** that the path to the SQL file must be specified relative to the project root, i.e. relative to `CARGO_MANIFEST_DIR`, even if you keep your SQL file alongside rust module that includes it. Because include-sql targets stable Rust this requirement will persist until [SourceFile][4] stabilizes.

# Under the Hood

After parsing and validating the content of the SQL file `include-sql` generates the following call:

```rust , ignore
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 (:user_id (&str) #book_ids (u32)) 
    " Updates the book records to reflect loan to a patron\n # Parameters\n * `user_id` - user ID\n * `book_ids` - book IDs"
    $ "UPDATE library\n   SET loaned_to = " :user_id "\n,     loaned_on = current_timestamp\n WHERE book_id IN (" #book_ids ")"
  }
}
```

Which `include_sqlite_sql::impl_sql` transforms into the following implementation:

```rust , ignore
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: Fn(&rusqlite::Row<'_>) -> rusqlite::Result<()>;

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

And, of course, it also implements the trait:

```rust , ignore
impl LibrarySql for rusqlite::Connection {
    /// ...
}
```

# Documentation

The included [documentation][5] describes the supported SQL file format and provides instructions on writing your own `impl_sql` macro.

[1]: https://github.com/krisajenkins/yesql
[2]: https://crates.io/crates/include-postgres-sql
[3]: https://crates.io/crates/include-sqlite-sql
[4]: https://doc.rust-lang.org/proc_macro/struct.SourceFile.html
[5]: https://quietboil.github.io/include-sql