From d7c8d43fb4a16801de64f25d591e49558632c351 Mon Sep 17 00:00:00 2001 From: Thom Chiovoloni Date: Tue, 29 Jan 2019 15:33:57 -0800 Subject: [PATCH] Add params/named_params macro, and expose ToSql from top level --- src/lib.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++---- src/statement.rs | 26 +++++++++++++++ 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index be4fdaa..f807944 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! extern crate time; //! //! use rusqlite::types::ToSql; -//! use rusqlite::{Connection, NO_PARAMS}; +//! use rusqlite::{Connection, params}; //! use time::Timespec; //! //! #[derive(Debug)] @@ -27,7 +27,7 @@ //! time_created TEXT NOT NULL, //! data BLOB //! )", -//! NO_PARAMS, +//! params![], //! ) //! .unwrap(); //! let me = Person { @@ -39,7 +39,7 @@ //! conn.execute( //! "INSERT INTO person (name, time_created, data) //! VALUES (?1, ?2, ?3)", -//! &[&me.name as &ToSql, &me.time_created, &me.data], +//! params![me.name, me.time_created, me.data], //! ) //! .unwrap(); //! @@ -47,7 +47,7 @@ //! .prepare("SELECT id, name, time_created, data FROM person") //! .unwrap(); //! let person_iter = stmt -//! .query_map(NO_PARAMS, |row| Person { +//! .query_map(params![], |row| Person { //! id: row.get(0), //! name: row.get(1), //! time_created: row.get(2), @@ -88,7 +88,9 @@ use std::sync::{Arc, Mutex, Once, ONCE_INIT}; use crate::cache::StatementCache; use crate::error::{error_from_handle, error_from_sqlite_code}; use crate::raw_statement::RawStatement; -use crate::types::{ToSql, ValueRef}; +use crate::types::ValueRef; + +pub use crate::types::ToSql; pub use crate::statement::{Statement, StatementStatus}; @@ -144,6 +146,77 @@ const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16; /// To be used when your statement has no [parameter](https://sqlite.org/lang_expr.html#varparam). pub const NO_PARAMS: &[&dyn ToSql] = &[]; + +/// A macro making it more convenient to pass heterogeneous lists +/// of parameters as a `&[&dyn ToSql]`. +/// +/// # Example +/// +/// ```rust,no_run +/// # use rusqlite::{Result, Connection, params}; +/// +/// struct Person { +/// name: String, +/// age_in_years: u8, +/// data: Option>, +/// } +/// +/// fn add_person(conn: &Connection, person: &Person) -> Result<()> { +/// conn.execute("INSERT INTO person (name, age_in_years, data) +/// VALUES (?1, ?2, ?3)", +/// params![person.name, person.age_in_years, person.data])?; +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! params { + () => { + $crate::NO_PARAMS + }; + ($($param:expr),+ $(,)?) => { + &[$(&$param as &dyn $crate::ToSql),+] + }; +} + +/// A macro making it more convenient to pass lists of named parameters +/// as a `&[(&str, &dyn ToSql)]`. +/// +/// # Example +/// +/// ```rust,no_run +/// # use rusqlite::{Result, Connection, named_params}; +/// +/// struct Person { +/// name: String, +/// age_in_years: u8, +/// data: Option>, +/// } +/// +/// fn add_person(conn: &Connection, person: &Person) -> Result<()> { +/// conn.execute_named( +/// "INSERT INTO person (name, age_in_years, data) +/// VALUES (:name, :age, :data)", +/// named_params!{ +/// ":name": person.name, +/// ":age": person.age_in_years, +/// ":data": person.data, +/// } +/// )?; +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! named_params { + () => { + &[] + }; + // Note: It's a lot more work to support this as part of the same macro as + // `params!`, unfortunately. + ($($param_name:literal: $param_val:expr),+ $(,)?) => { + &[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] + }; +} + /// A typedef of the result returned by many methods. pub type Result = result::Result; @@ -1644,7 +1717,7 @@ mod test { let i_to_insert = i as i64; assert_eq!( insert_stmt - .execute(&[&i_to_insert as &dyn ToSql, &v]) + .execute(params![i_to_insert, v]) .unwrap(), 1 ); @@ -1902,7 +1975,7 @@ mod test { END;"; db.execute_batch(sql).unwrap(); - db.query_row("SELECT * FROM foo", NO_PARAMS, |r| { + db.query_row("SELECT * FROM foo", params![], |r| { assert_eq!(2, r.column_count()) }) .unwrap(); diff --git a/src/statement.rs b/src/statement.rs index 4d69fbb..1048c30 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -112,6 +112,17 @@ impl<'conn> Statement<'conn> { /// } /// ``` /// + /// Note, the `named_params` macro is provided for syntactic convenience, and + /// so the above example could also be written as: + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, named_params}; + /// fn insert(conn: &Connection) -> Result { + /// let mut stmt = conn.prepare("INSERT INTO test (name) VALUES (:name)")?; + /// stmt.execute_named(named_params!{":name": "one"}) + /// } + /// ``` + /// /// # Failure /// /// Will return `Err` if binding parameters fails, the executed statement @@ -205,6 +216,21 @@ impl<'conn> Statement<'conn> { /// } /// ``` /// + /// Note, the `named_params!` macro is provided for syntactic convenience, and + /// so the above example could also be written as: + /// + /// ```rust,no_run + /// # use rusqlite::{Connection, Result, named_params}; + /// fn query(conn: &Connection) -> Result<()> { + /// let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?; + /// let mut rows = stmt.query_named(named_params!{ ":name": "one" })?; + /// while let Some(row) = rows.next() { + /// // ... + /// } + /// Ok(()) + /// } + /// ``` + /// /// # Failure /// /// Will return `Err` if binding parameters fails.