Skip to content

Commit ae0e2fd

Browse files
committed
Add query_one
1 parent 2adc7c1 commit ae0e2fd

4 files changed

Lines changed: 78 additions & 1 deletion

File tree

src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ pub enum Error {
5050
/// for [`query_row`](crate::Connection::query_row)) did not return any.
5151
QueryReturnedNoRows,
5252

53+
/// Error when a query that was expected to return only one row (e.g.,
54+
/// for [`query_one`](crate::Connection::query_one)) did return more than one.
55+
QueryReturnedMoreThanOneRow,
56+
5357
/// Error when the value of a particular column is requested, but the index
5458
/// is out of range for the statement.
5559
InvalidColumnIndex(usize),
@@ -152,6 +156,7 @@ impl PartialEq for Error {
152156
(Self::InvalidPath(p1), Self::InvalidPath(p2)) => p1 == p2,
153157
(Self::ExecuteReturnedResults, Self::ExecuteReturnedResults) => true,
154158
(Self::QueryReturnedNoRows, Self::QueryReturnedNoRows) => true,
159+
(Self::QueryReturnedMoreThanOneRow, Self::QueryReturnedMoreThanOneRow) => true,
155160
(Self::InvalidColumnIndex(i1), Self::InvalidColumnIndex(i2)) => i1 == i2,
156161
(Self::InvalidColumnName(n1), Self::InvalidColumnName(n2)) => n1 == n2,
157162
(Self::InvalidColumnType(i1, n1, t1), Self::InvalidColumnType(i2, n2, t2)) => {
@@ -278,6 +283,7 @@ impl fmt::Display for Error {
278283
write!(f, "Execute returned results - did you mean to call query?")
279284
}
280285
Self::QueryReturnedNoRows => write!(f, "Query returned no rows"),
286+
Self::QueryReturnedMoreThanOneRow => write!(f, "Query returned more than one row"),
281287
Self::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
282288
Self::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
283289
Self::InvalidColumnType(i, ref name, ref t) => {
@@ -336,6 +342,7 @@ impl error::Error for Error {
336342
| Self::InvalidParameterName(_)
337343
| Self::ExecuteReturnedResults
338344
| Self::QueryReturnedNoRows
345+
| Self::QueryReturnedMoreThanOneRow
339346
| Self::InvalidColumnIndex(_)
340347
| Self::InvalidColumnName(_)
341348
| Self::InvalidColumnType(..)

src/functions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,7 @@ mod test {
10051005
})?;
10061006

10071007
let res: bool =
1008-
db.one_column("SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)")?;
1008+
db.first_column("SELECT example(0, i) FROM (SELECT 0 as i UNION SELECT 1)")?;
10091009
// Doesn't actually matter, we'll assert in the function if there's a problem.
10101010
assert!(res);
10111011
Ok(())

src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,9 +665,36 @@ impl Connection {
665665
stmt.query_row(params, f)
666666
}
667667

668+
/// Convenience method to execute a query that is expected to return exactly
669+
/// one row.
670+
///
671+
/// Returns `Err(QueryReturnedMoreThanOneRow)` if the query returns more than one row.
672+
///
673+
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
674+
/// query truly is optional, you can call
675+
/// [`.optional()`](crate::OptionalExtension::optional) on the result of
676+
/// this to get a `Result<Option<T>>` (requires that the trait
677+
/// `rusqlite::OptionalExtension` is imported).
678+
///
679+
/// # Failure
680+
///
681+
/// Will return `Err` if the underlying SQLite call fails.
682+
pub fn query_one<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T>
683+
where
684+
P: Params,
685+
F: FnOnce(&Row<'_>) -> Result<T>,
686+
{
687+
let mut stmt = self.prepare(sql)?;
688+
stmt.query_one(params, f)
689+
}
690+
668691
// https://sqlite.org/tclsqlite.html#onecolumn
669692
#[cfg(test)]
670693
pub(crate) fn one_column<T: types::FromSql>(&self, sql: &str) -> Result<T> {
694+
self.query_one(sql, [], |r| r.get(0))
695+
}
696+
#[cfg(test)]
697+
pub(crate) fn first_column<T: types::FromSql>(&self, sql: &str) -> Result<T> {
671698
self.query_row(sql, [], |r| r.get(0))
672699
}
673700

src/statement.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,33 @@ impl Statement<'_> {
381381
rows.get_expected_row().and_then(f)
382382
}
383383

384+
/// Convenience method to execute a query that is expected to return exactly
385+
/// one row.
386+
///
387+
/// Returns `Err(QueryReturnedMoreThanOneRow)` if the query returns more than one row.
388+
///
389+
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
390+
/// query truly is optional, you can call
391+
/// [`.optional()`](crate::OptionalExtension::optional) on the result of
392+
/// this to get a `Result<Option<T>>` (requires that the trait
393+
/// `rusqlite::OptionalExtension` is imported).
394+
///
395+
/// # Failure
396+
///
397+
/// Will return `Err` if the underlying SQLite call fails.
398+
pub fn query_one<T, P, F>(&mut self, params: P, f: F) -> Result<T>
399+
where
400+
P: Params,
401+
F: FnOnce(&Row<'_>) -> Result<T>,
402+
{
403+
let mut rows = self.query(params)?;
404+
let row = rows.get_expected_row().and_then(f)?;
405+
if rows.next()?.is_some() {
406+
return Err(Error::QueryReturnedMoreThanOneRow);
407+
}
408+
Ok(row)
409+
}
410+
384411
/// Consumes the statement.
385412
///
386413
/// Functionally equivalent to the `Drop` implementation, but allows
@@ -1170,6 +1197,22 @@ mod test {
11701197
Ok(())
11711198
}
11721199

1200+
#[test]
1201+
fn query_one() -> Result<()> {
1202+
let db = Connection::open_in_memory()?;
1203+
db.execute_batch("CREATE TABLE foo(x INTEGER, y INTEGER);")?;
1204+
let mut stmt = db.prepare("SELECT y FROM foo WHERE x = ?1")?;
1205+
let y: Result<i64> = stmt.query_one([1i32], |r| r.get(0));
1206+
assert_eq!(Error::QueryReturnedNoRows, y.unwrap_err());
1207+
db.execute_batch("INSERT INTO foo VALUES(1, 3);")?;
1208+
let y: Result<i64> = stmt.query_one([1i32], |r| r.get(0));
1209+
assert_eq!(3i64, y?);
1210+
db.execute_batch("INSERT INTO foo VALUES(1, 3);")?;
1211+
let y: Result<i64> = stmt.query_one([1i32], |r| r.get(0));
1212+
assert_eq!(Error::QueryReturnedMoreThanOneRow, y.unwrap_err());
1213+
Ok(())
1214+
}
1215+
11731216
#[test]
11741217
fn test_query_by_column_name() -> Result<()> {
11751218
let db = Connection::open_in_memory()?;

0 commit comments

Comments
 (0)