mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-22 16:29:20 +08:00
Implement Params
for tuples of ToSql up to size 16, and touch up docs
This commit is contained in:
parent
8db9aff358
commit
2ec0b2e8fe
@ -1,5 +1,6 @@
|
||||
[package]
|
||||
name = "rusqlite"
|
||||
# Note: Update version in README.md when you change this.
|
||||
version = "0.27.0"
|
||||
authors = ["The rusqlite developers"]
|
||||
edition = "2018"
|
||||
|
41
README.md
41
README.md
@ -30,7 +30,7 @@ fn main() -> Result<()> {
|
||||
name TEXT NOT NULL,
|
||||
data BLOB
|
||||
)",
|
||||
[],
|
||||
[], // empty list of parameters.
|
||||
)?;
|
||||
let me = Person {
|
||||
id: 0,
|
||||
@ -69,34 +69,45 @@ newer SQLite version; see details below.
|
||||
Rusqlite provides several features that are behind [Cargo
|
||||
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.
|
||||
* [`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.
|
||||
* [`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.
|
||||
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`.)
|
||||
* [`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
|
||||
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
|
||||
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.
|
||||
* `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).
|
||||
* `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).
|
||||
* `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).
|
||||
* `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).
|
||||
|
||||
* `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`.
|
||||
* `bundled-sqlcipher` uses a bundled version of SQLCipher. This searches for and links against a system-installed crypto library to provide the crypto implementation.
|
||||
|
@ -29,7 +29,7 @@
|
||||
//! };
|
||||
//! conn.execute(
|
||||
//! "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")?;
|
||||
@ -145,7 +145,7 @@ const STATEMENT_CACHE_DEFAULT_CAPACITY: usize = 16;
|
||||
#[deprecated = "Use an empty array instead; `stmt.execute(NO_PARAMS)` => `stmt.execute([])`"]
|
||||
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]`.
|
||||
///
|
||||
/// # Example
|
||||
@ -161,8 +161,7 @@ pub const NO_PARAMS: &[&dyn ToSql] = &[];
|
||||
///
|
||||
/// fn add_person(conn: &Connection, person: &Person) -> Result<()> {
|
||||
/// conn.execute(
|
||||
/// "INSERT INTO person (name, age_in_years, data)
|
||||
/// VALUES (?1, ?2, ?3)",
|
||||
/// "INSERT INTO person(name, age_in_years, data) VALUES (?1, ?2, ?3)",
|
||||
/// params![person.name, person.age_in_years, person.data],
|
||||
/// )?;
|
||||
/// Ok(())
|
||||
|
126
src/params.rs
126
src/params.rs
@ -31,12 +31,19 @@ use sealed::Sealed;
|
||||
/// parameters is known at compile time, this can be done in one of the
|
||||
/// 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.
|
||||
/// `thing.query(rusqlite::params![1, "foo", bar])`. This is mostly useful for
|
||||
/// heterogeneous lists of parameters, or lists where the number of parameters
|
||||
/// exceeds 32.
|
||||
/// heterogeneous lists where the number of parameters greater than 16, or
|
||||
/// 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",
|
||||
/// "bar", "baz"])`.
|
||||
@ -65,6 +72,9 @@ use sealed::Sealed;
|
||||
/// fn update_rows(conn: &Connection) -> Result<()> {
|
||||
/// let mut stmt = conn.prepare("INSERT INTO test (a, b) VALUES (?, ?)")?;
|
||||
///
|
||||
/// // Using a tuple:
|
||||
/// stmt.execute((0, "foobar"))?;
|
||||
///
|
||||
/// // Using `rusqlite::params!`:
|
||||
/// stmt.execute(params![1i32, "blah"])?;
|
||||
///
|
||||
@ -127,12 +137,26 @@ use sealed::Sealed;
|
||||
///
|
||||
/// ## No parameters
|
||||
///
|
||||
/// You can just use an empty array literal for no params. The
|
||||
/// `rusqlite::NO_PARAMS` constant which was so common in previous versions of
|
||||
/// this library is no longer needed (and is now deprecated).
|
||||
/// You can just use an empty tuple or the empty array literal to run a query
|
||||
/// that accepts no parameters. (The `rusqlite::NO_PARAMS` constant which was
|
||||
/// common in previous versions of this library is no longer needed, and is now
|
||||
/// deprecated).
|
||||
///
|
||||
/// ### 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
|
||||
/// # use rusqlite::{Connection, Result, params};
|
||||
/// 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
|
||||
/// 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
|
||||
/// annoying.
|
||||
/// - Use a `&[&dyn ToSql]`. This is often annoying to construct if you don't
|
||||
/// already have this type on-hand.
|
||||
/// - 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
|
||||
/// 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
|
||||
// unambiguous, this must be the *only* implementation for an empty array. This
|
||||
// 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 Params for [&(dyn ToSql + Send + Sync); 0] {
|
||||
#[inline]
|
||||
fn __bind_in(self, stmt: &mut Statement<'_>) -> Result<()> {
|
||||
// Note: Can't just return `Ok(())` — `Statement::bind_parameters`
|
||||
// checks that the right number of params were passed too.
|
||||
// TODO: we should have tests for `Error::InvalidParameterCount`...
|
||||
stmt.bind_parameters(&[] as &[&dyn ToSql])
|
||||
stmt.ensure_parameter_count(0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
($($N:literal)+) => {$(
|
||||
// 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 `[_;
|
||||
// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
|
||||
// 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!(
|
||||
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
|
||||
|
@ -33,19 +33,47 @@ impl Statement<'_> {
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result, params};
|
||||
/// 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
|
||||
/// // 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])?;
|
||||
/// // However, it's not required, many cases are fine as:
|
||||
/// stmt.execute(&[&2i32])?;
|
||||
/// // Or even:
|
||||
/// stmt.execute([2i32])?;
|
||||
/// // If you really want to, this is an option as well.
|
||||
/// stmt.execute((2i32,))?;
|
||||
/// 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
|
||||
///
|
||||
/// ```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]
|
||||
pub(crate) fn bind_parameters_named<T: ?Sized + ToSql>(
|
||||
&mut self,
|
||||
@ -606,9 +644,14 @@ impl Statement<'_> {
|
||||
/// - binding named and positional parameters in the same query.
|
||||
/// - separating parameter binding from query execution.
|
||||
///
|
||||
/// Statements that have had their parameters bound this way should be
|
||||
/// queried or executed by [`Statement::raw_query`] or
|
||||
/// [`Statement::raw_execute`]. Other functions are not guaranteed to work.
|
||||
/// In general, statements that have had *any* parameters bound this way
|
||||
/// should have *all* parameters bound this way, and be queried or executed
|
||||
/// 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
|
||||
///
|
||||
@ -1266,6 +1309,41 @@ mod test {
|
||||
assert!(!stmt.exists([0i32])?);
|
||||
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]
|
||||
fn test_query_row() -> Result<()> {
|
||||
|
Loading…
Reference in New Issue
Block a user