Implement Params for tuples of ToSql up to size 16, and touch up docs

This commit is contained in:
Thom Chiovoloni 2022-03-06 21:25:10 -08:00
parent 8db9aff358
commit 2ec0b2e8fe
5 changed files with 234 additions and 45 deletions

View File

@ -1,5 +1,6 @@
[package] [package]
name = "rusqlite" name = "rusqlite"
# Note: Update version in README.md when you change this.
version = "0.27.0" version = "0.27.0"
authors = ["The rusqlite developers"] authors = ["The rusqlite developers"]
edition = "2018" edition = "2018"

View File

@ -30,7 +30,7 @@ fn main() -> Result<()> {
name TEXT NOT NULL, name TEXT NOT NULL,
data BLOB data BLOB
)", )",
[], [], // empty list of parameters.
)?; )?;
let me = Person { let me = Person {
id: 0, id: 0,
@ -69,34 +69,45 @@ newer SQLite version; see details below.
Rusqlite provides several features that are behind [Cargo Rusqlite provides several features that are behind [Cargo
features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are:
* [`load_extension`](https://docs.rs/rusqlite/~0/rusqlite/struct.LoadExtensionGuard.html) * [`load_extension`](https://docs.rs/rusqlite/latest/rusqlite/struct.LoadExtensionGuard.html)
allows loading dynamic library-based SQLite extensions. allows loading dynamic library-based SQLite extensions.
* [`backup`](https://docs.rs/rusqlite/~0/rusqlite/backup/index.html)
* [`backup`](https://docs.rs/rusqlite/latest/rusqlite/backup/index.html)
allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later. allows use of SQLite's online backup API. Note: This feature requires SQLite 3.6.11 or later.
* [`functions`](https://docs.rs/rusqlite/~0/rusqlite/functions/index.html)
* [`functions`](https://docs.rs/rusqlite/latest/rusqlite/functions/index.html)
allows you to load Rust closures into SQLite connections for use in queries. allows you to load Rust closures into SQLite connections for use in queries.
Note: This feature requires SQLite 3.7.3 or later. Note: This feature requires SQLite 3.7.3 or later.
* `window` for [window function](https://www.sqlite.org/windowfunctions.html) support (`fun(...) OVER ...`). (Implies `functions`.) * `window` for [window function](https://www.sqlite.org/windowfunctions.html) support (`fun(...) OVER ...`). (Implies `functions`.)
* [`trace`](https://docs.rs/rusqlite/~0/rusqlite/trace/index.html)
* [`trace`](https://docs.rs/rusqlite/latest/rusqlite/trace/index.html)
allows hooks into SQLite's tracing and profiling APIs. Note: This feature allows hooks into SQLite's tracing and profiling APIs. Note: This feature
requires SQLite 3.6.23 or later. requires SQLite 3.6.23 or later.
* [`blob`](https://docs.rs/rusqlite/~0/rusqlite/blob/index.html)
* [`blob`](https://docs.rs/rusqlite/latest/rusqlite/blob/index.html)
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. Note: This feature
requires SQLite 3.7.4 or later. requires SQLite 3.7.4 or later.
* [`limits`](https://docs.rs/rusqlite/~0/rusqlite/struct.Connection.html#method.limit)
* [`limits`](https://docs.rs/rusqlite/latest/rusqlite/struct.Connection.html#method.limit)
allows you to set and retrieve SQLite's per connection limits. allows you to set and retrieve SQLite's per connection limits.
* `chrono` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for various * `chrono` implements [`FromSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.ToSql.html) for various
types from the [`chrono` crate](https://crates.io/crates/chrono). types from the [`chrono` crate](https://crates.io/crates/chrono).
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the * `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.ToSql.html) for the
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json). `Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
* `time` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the * `time` implements [`FromSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.ToSql.html) for the
`time::OffsetDateTime` type from the [`time` crate](https://crates.io/crates/time). `time::OffsetDateTime` type from the [`time` crate](https://crates.io/crates/time).
* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the * `url` implements [`FromSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/latest/rusqlite/types/trait.ToSql.html) for the
`Url` type from the [`url` crate](https://crates.io/crates/url). `Url` type from the [`url` crate](https://crates.io/crates/url).
* `bundled` uses a bundled version of SQLite. This is a good option for cases where linking to SQLite is complicated, such as Windows. * `bundled` uses a bundled version of SQLite. This is a good option for cases where linking to SQLite is complicated, such as Windows.
* `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature overrides `bundled`. * `sqlcipher` looks for the SQLCipher library to link against instead of SQLite. This feature overrides `bundled`.
* `bundled-sqlcipher` uses a bundled version of SQLCipher. This searches for and links against a system-installed crypto library to provide the crypto implementation. * `bundled-sqlcipher` uses a bundled version of SQLCipher. This searches for and links against a system-installed crypto library to provide the crypto implementation.

View File

@ -29,7 +29,7 @@
//! }; //! };
//! conn.execute( //! conn.execute(
//! "INSERT INTO person (name, data) VALUES (?1, ?2)", //! "INSERT INTO person (name, data) VALUES (?1, ?2)",
//! params![me.name, me.data], //! (me.name, me.data),
//! )?; //! )?;
//! //!
//! let mut stmt = conn.prepare("SELECT id, name, data FROM person")?; //! let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
@ -145,7 +145,7 @@ const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
#[deprecated = "Use an empty array instead; `stmt.execute(NO_PARAMS)` => `stmt.execute([])`"] #[deprecated = "Use an empty array instead; `stmt.execute(NO_PARAMS)` => `stmt.execute([])`"]
pub const NO_PARAMS: &[&dyn ToSql] = &[]; pub const NO_PARAMS: &[&dyn ToSql] = &[];
/// A macro making it more convenient to pass heterogeneous or long lists of /// A macro making it more convenient to longer lists of
/// parameters as a `&[&dyn ToSql]`. /// parameters as a `&[&dyn ToSql]`.
/// ///
/// # Example /// # Example
@ -161,8 +161,7 @@ pub const NO_PARAMS: &[&dyn ToSql] = &[];
/// ///
/// fn add_person(conn: &Connection, person: &Person) -> Result<()> { /// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
/// conn.execute( /// conn.execute(
/// "INSERT INTO person (name, age_in_years, data) /// "INSERT INTO person(name, age_in_years, data) VALUES (?1, ?2, ?3)",
/// VALUES (?1, ?2, ?3)",
/// params![person.name, person.age_in_years, person.data], /// params![person.name, person.age_in_years, person.data],
/// )?; /// )?;
/// Ok(()) /// Ok(())

View File

@ -31,12 +31,19 @@ use sealed::Sealed;
/// parameters is known at compile time, this can be done in one of the /// parameters is known at compile time, this can be done in one of the
/// following ways: /// following ways:
/// ///
/// - For small lists of parameters up to 16 items, they may alternatively be
/// passed as a tuple, as in `thing.query((1, "foo"))`.
///
/// This is somewhat inconvenient for a single item, since you need a
/// weird-looking trailing comma: `thing.query(("example",))`. That case is
/// perhaps more cleanly expressed as `thing.query(["example"])`.
///
/// - Using the [`rusqlite::params!`](crate::params!) macro, e.g. /// - Using the [`rusqlite::params!`](crate::params!) macro, e.g.
/// `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for /// `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for
/// heterogeneous lists of parameters, or lists where the number of parameters /// heterogeneous lists where the number of parameters greater than 16, or
/// exceeds 32. /// homogenous lists of paramters where the number of parameters exceeds 32.
/// ///
/// - For small heterogeneous lists of parameters, they can either be passed as: /// - For small homogeneous lists of parameters, they can either be passed as:
/// ///
/// - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo", /// - an array, as in `thing.query([1i32, 2, 3, 4])` or `thing.query(["foo",
/// "bar", "baz"])`. /// "bar", "baz"])`.
@ -65,6 +72,9 @@ use sealed::Sealed;
/// fn update_rows(conn: &Connection) -> Result<()> { /// fn update_rows(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?; /// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
/// ///
/// // Using a tuple:
/// stmt.execute((0, "foobar"))?;
///
/// // Using `rusqlite::params!`: /// // Using `rusqlite::params!`:
/// stmt.execute(params![1i32, "blah"])?; /// stmt.execute(params![1i32, "blah"])?;
/// ///
@ -127,12 +137,26 @@ use sealed::Sealed;
/// ///
/// ## No parameters /// ## No parameters
/// ///
/// You can just use an empty array literal for no params. The /// You can just use an empty tuple or the empty array literal to run a query
/// `rusqlite::NO_PARAMS` constant which was so common in previous versions of /// that accepts no parameters. (The `rusqlite::NO_PARAMS` constant which was
/// this library is no longer needed (and is now deprecated). /// common in previous versions of this library is no longer needed, and is now
/// deprecated).
/// ///
/// ### Example (no parameters) /// ### Example (no parameters)
/// ///
/// The empty tuple:
///
/// ```rust,no_run
/// # use rusqlite::{Connection, Result, params};
/// fn delete_all_users(conn: &Connection) -> Result<()> {
/// // You may also use `()`.
/// conn.execute("DELETE FROM users", ())?;
/// Ok(())
/// }
/// ```
///
/// The empty array:
///
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result, params}; /// # use rusqlite::{Connection, Result, params};
/// fn delete_all_users(conn: &Connection) -> Result<()> { /// fn delete_all_users(conn: &Connection) -> Result<()> {
@ -147,10 +171,11 @@ use sealed::Sealed;
/// If you have a number of parameters which is unknown at compile time (for /// If you have a number of parameters which is unknown at compile time (for
/// example, building a dynamic query at runtime), you have two choices: /// example, building a dynamic query at runtime), you have two choices:
/// ///
/// - Use a `&[&dyn ToSql]`, which is nice if you have one otherwise might be /// - Use a `&[&dyn ToSql]`. This is often annoying to construct if you don't
/// annoying. /// already have this type on-hand.
/// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an /// - Use the [`ParamsFromIter`] type. This essentially lets you wrap an
/// iterator some `T: ToSql` with something that implements `Params`. /// iterator some `T: ToSql` with something that implements `Params`. The
/// usage of this looks like `rusqlite::params_from_iter(something)`.
/// ///
/// A lot of the considerations here are similar either way, so you should see /// A lot of the considerations here are similar either way, so you should see
/// the [`ParamsFromIter`] documentation for more info / examples. /// the [`ParamsFromIter`] documentation for more info / examples.
@ -169,14 +194,23 @@ pub trait Params: Sealed {
// Explicitly impl for empty array. Critically, for `conn.execute([])` to be // Explicitly impl for empty array. Critically, for `conn.execute([])` to be
// unambiguous, this must be the *only* implementation for an empty array. This // unambiguous, this must be the *only* implementation for an empty array. This
// avoids `NO_PARAMS` being a necessary part of the API. // avoids `NO_PARAMS` being a necessary part of the API.
//
// This sadly prevents `impl<T: ToSql, const N: usize> Params for [T; N]`, which
// forces people to use `params![...]` or `rusqlite::params_from_iter` for long
// homogenous lists of parameters. This is not that big of a deal, but is
// unfortunate, especially because I mostly did it because I wanted a simple
// syntax for no-params that didnt require importing -- the empty tuple fits
// that nicely, but I didn't think of it until much later.
//
// Admittedly, if we did have the generic impl, then we *wouldn't* support the
// empty array literal as a parameter, since the `T` there would fail to be
// inferred. The error message here would probably be quite bad, and so on
// further thought, probably would end up causing *more* surprises, not less.
impl Sealed for [&(dyn ToSql + Send + Sync); 0] {} impl Sealed for [&(dyn ToSql + Send + Sync); 0] {}
impl Params for [&(dyn ToSql + Send + Sync); 0] { impl Params for [&(dyn ToSql + Send + Sync); 0] {
#[inline] #[inline]
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> { fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
// Note: Can't just return `Ok(())` — `Statement::bind_parameters` stmt.ensure_parameter_count(0)
// checks that the right number of params were passed too.
// TODO: we should have tests for `Error::InvalidParameterCount`...
stmt.bind_parameters(&[] as &[&dyn ToSql])
} }
} }
@ -196,6 +230,69 @@ impl Params for &[(&str, &dyn ToSql)] {
} }
} }
// Manual impls for the empty and singleton tuple, although the rest are covered
// by macros.
impl Sealed for () {}
impl Params for () {
#[inline]
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
stmt.ensure_parameter_count(0)
}
}
// I'm pretty sure you could tweak the `single_tuple_impl` to accept this.
impl<T: ToSql> Sealed for (T,) {}
impl<T: ToSql> Params for (T,) {
#[inline]
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
stmt.ensure_parameter_count(1)?;
stmt.raw_bind_parameter(1, self.0)?;
Ok(())
}
}
macro_rules! single_tuple_impl {
($count:literal : $(($field:tt $ftype:ident)),* $(,)?) => {
impl<$($ftype,)*> Sealed for ($($ftype,)*) where $($ftype: ToSql,)* {}
impl<$($ftype,)*> Params for ($($ftype,)*) where $($ftype: ToSql,)* {
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
stmt.ensure_parameter_count($count)?;
$({
debug_assert!($field < $count);
stmt.raw_bind_parameter($field + 1, self.$field)?;
})+
Ok(())
}
}
}
}
// We use a the macro for the rest, but don't bother with trying to implement it
// in a single invocation (it's possible to do, but my attempts were almost the
// same amount of code as just writing it out this way, and much more dense --
// it is a more complicated case than the TryFrom macro we have for row->tuple).
//
// Note that going up to 16 (rather than the 12 that the impls in the stdlib
// usually support) is just because we did the same in the `TryFrom<Row>` impl.
// I didn't catch that then, but there's no reason to remove it, and it seems
// nice to be consistent here; this way putting data in the database and getting
// data out of the database are more symmetric in a (mostly superficial) sense.
single_tuple_impl!(2: (0 A), (1 B));
single_tuple_impl!(3: (0 A), (1 B), (2 C));
single_tuple_impl!(4: (0 A), (1 B), (2 C), (3 D));
single_tuple_impl!(5: (0 A), (1 B), (2 C), (3 D), (4 E));
single_tuple_impl!(6: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F));
single_tuple_impl!(7: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));
single_tuple_impl!(8: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));
single_tuple_impl!(9: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));
single_tuple_impl!(10: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));
single_tuple_impl!(11: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));
single_tuple_impl!(12: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));
single_tuple_impl!(13: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));
single_tuple_impl!(14: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));
single_tuple_impl!(15: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));
single_tuple_impl!(16: (0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));
macro_rules! impl_for_array_ref { macro_rules! impl_for_array_ref {
($($N:literal)+) => {$( ($($N:literal)+) => {$(
// These are already generic, and there's a shedload of them, so lets // These are already generic, and there's a shedload of them, so lets
@ -225,6 +322,9 @@ macro_rules! impl_for_array_ref {
// Following libstd/libcore's (old) lead, implement this for arrays up to `[_; // Following libstd/libcore's (old) lead, implement this for arrays up to `[_;
// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the // 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
// note above the impl of `[&dyn ToSql; 0]` for more information. // note above the impl of `[&dyn ToSql; 0]` for more information.
//
// Note that this unfortunately means we can't use const generics here, but I
// don't really think it matters -- users who hit that can use `params!` anyway.
impl_for_array_ref!( impl_for_array_ref!(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19 20 21 22 23 24 25 26 27 29 30 31 32 18 19 20 21 22 23 24 25 26 27 29 30 31 32

View File

@ -33,19 +33,47 @@ impl Statement<'_> {
/// ```rust,no_run /// ```rust,no_run
/// # use rusqlite::{Connection, Result, params}; /// # use rusqlite::{Connection, Result, params};
/// fn update_rows(conn: &Connection) -> Result<()> { /// fn update_rows(conn: &Connection) -> Result<()> {
/// let mut stmt = conn.prepare("UPDATE foo SET bar = 'baz' WHERE qux = ?")?; /// let mut stmt = conn.prepare("UPDATE foo SET bar = ? WHERE qux = ?")?;
/// // For a single parameter, or a parameter where all the values have
/// // the same type, just passing an array is simplest.
/// stmt.execute([2i32])?;
/// // The `rusqlite::params!` macro is mostly useful when the parameters do not /// // The `rusqlite::params!` macro is mostly useful when the parameters do not
/// // all have the same type, or if there are more than 32 parameters /// // all have the same type, or if there are more than 32 parameters
/// // at once. /// // at once, but it can be used in other cases.
/// stmt.execute(params![1i32])?; /// stmt.execute(params![1i32])?;
/// // However, it's not required, many cases are fine as: /// // However, it's not required, many cases are fine as:
/// stmt.execute(&[&2i32])?; /// stmt.execute(&[&2i32])?;
/// // Or even: /// // Or even:
/// stmt.execute([2i32])?; /// stmt.execute([2i32])?;
/// // If you really want to, this is an option as well.
/// stmt.execute((2i32,))?;
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
/// ///
/// #### Heterogeneous positional parameters
///
/// ```
/// use rusqlite::{Connection, Result};
/// fn store_file(conn: &Connection, path: &str, data: &[u8]) -> Result<()> {
/// # // no need to do it for real.
/// # fn sha256(_: &[u8]) -> [u8; 32] { [0; 32] }
/// let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?, ?, ?)";
/// let mut stmt = conn.prepare_cached(query)?;
/// let hash: [u8; 32] = sha256(data);
/// // The easiest way to pass positional parameters of have several
/// // different types is by using a tuple.
/// stmt.execute((path, hash, data))?;
/// // Using the `params!` macro also works, and supports longer parameter lists:
/// stmt.execute(rusqlite::params![path, hash, data])?;
/// Ok(())
/// }
/// # let c = Connection::open_in_memory().unwrap();
/// # c.execute_batch("CREATE TABLE files(path TEXT PRIMARY KEY, hash BLOB, data BLOB)").unwrap();
/// # store_file(&c, "foo/bar.txt", b"bibble").unwrap();
/// # store_file(&c, "foo/baz.txt", b"bobble").unwrap();
/// ```
///
/// ### Use with named parameters /// ### Use with named parameters
/// ///
/// ```rust,no_run /// ```rust,no_run
@ -566,6 +594,16 @@ impl Statement<'_> {
} }
} }
#[inline]
pub(crate) fn ensure_parameter_count(&self, n: usize) -> Result<()> {
let count = self.parameter_count();
if count != n {
Err(Error::InvalidParameterCount(n, count))
} else {
Ok(())
}
}
#[inline] #[inline]
pub(crate) fn bind_parameters_named<T: ?Sized + ToSql>( pub(crate) fn bind_parameters_named<T: ?Sized + ToSql>(
&mut self, &mut self,
@ -606,9 +644,14 @@ impl Statement<'_> {
/// - binding named and positional parameters in the same query. /// - binding named and positional parameters in the same query.
/// - separating parameter binding from query execution. /// - separating parameter binding from query execution.
/// ///
/// Statements that have had their parameters bound this way should be /// In general, statements that have had *any* parameters bound this way
/// queried or executed by [`Statement::raw_query`] or /// should have *all* parameters bound this way, and be queried or executed
/// [`Statement::raw_execute`]. Other functions are not guaranteed to work. /// by [`Statement::raw_query`] or [`Statement::raw_execute`], other usage
/// is unsupported and will likely, probably in surprising ways.
///
/// That is: Do not mix the "raw" statement functions with the rest of the
/// API, or the results may be surprising, and may even change in future
/// versions without comment.
/// ///
/// # Example /// # Example
/// ///
@ -1266,6 +1309,41 @@ mod test {
assert!(!stmt.exists([0i32])?); assert!(!stmt.exists([0i32])?);
Ok(()) Ok(())
} }
#[test]
fn test_tuple_params() -> Result<()> {
let db = Connection::open_in_memory()?;
let s = db.query_row("SELECT printf('[%s]', ?)", ("abc",), |r| {
r.get::<_, String>(0)
})?;
assert_eq!(s, "[abc]");
let s = db.query_row(
"SELECT printf('%d %s %d', ?, ?, ?)",
(1i32, "abc", 2i32),
|r| r.get::<_, String>(0),
)?;
assert_eq!(s, "1 abc 2");
let s = db.query_row(
"SELECT printf('%d %s %d %d', ?, ?, ?, ?)",
(1, "abc", 2i32, 4i64),
|r| r.get::<_, String>(0),
)?;
assert_eq!(s, "1 abc 2 4");
#[rustfmt::skip]
let bigtup = (
0, "a", 1, "b", 2, "c", 3, "d",
4, "e", 5, "f", 6, "g", 7, "h",
);
let query = "SELECT printf(
'%d %s | %d %s | %d %s | %d %s || %d %s | %d %s | %d %s | %d %s',
?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?, ?
)";
let s = db.query_row(query, bigtup, |r| r.get::<_, String>(0))?;
assert_eq!(s, "0 a | 1 b | 2 c | 3 d || 4 e | 5 f | 6 g | 7 h");
Ok(())
}
#[test] #[test]
fn test_query_row() -> Result<()> { fn test_query_row() -> Result<()> {