Skip to content

Commit 8abf96c

Browse files
committed
Add query_one convenience method
Which works exactly like https://docs.rs/rusqlite/latest/rusqlite/struct.Statement.html#method.query_one
1 parent 2b5c932 commit 8abf96c

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

crates/duckdb/src/error.rs

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

50+
/// Error when a query that was expected to return only one row (e.g.,
51+
/// for [`query_one`](crate::Connection::query_one)) did return more than one.
52+
QueryReturnedMoreThanOneRow,
53+
5054
/// Error when the value of a particular column is requested, but the index
5155
/// is out of range for the statement.
5256
InvalidColumnIndex(usize),
@@ -96,6 +100,7 @@ impl PartialEq for Error {
96100
(Self::InvalidPath(p1), Self::InvalidPath(p2)) => p1 == p2,
97101
(Self::ExecuteReturnedResults, Self::ExecuteReturnedResults) => true,
98102
(Self::QueryReturnedNoRows, Self::QueryReturnedNoRows) => true,
103+
(Self::QueryReturnedMoreThanOneRow, Self::QueryReturnedMoreThanOneRow) => true,
99104
(Self::InvalidColumnIndex(i1), Self::InvalidColumnIndex(i2)) => i1 == i2,
100105
(Self::InvalidColumnName(n1), Self::InvalidColumnName(n2)) => n1 == n2,
101106
(Self::InvalidColumnType(i1, n1, t1), Self::InvalidColumnType(i2, n2, t2)) => {
@@ -170,6 +175,7 @@ impl fmt::Display for Error {
170175
write!(f, "Execute returned results - did you mean to call query?")
171176
}
172177
Self::QueryReturnedNoRows => write!(f, "Query returned no rows"),
178+
Self::QueryReturnedMoreThanOneRow => write!(f, "Query returned more than one row"),
173179
Self::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
174180
Self::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
175181
Self::InvalidColumnType(i, ref name, ref t) => {
@@ -201,6 +207,7 @@ impl error::Error for Error {
201207
| Self::InvalidParameterName(_)
202208
| Self::ExecuteReturnedResults
203209
| Self::QueryReturnedNoRows
210+
| Self::QueryReturnedMoreThanOneRow
204211
| Self::InvalidColumnIndex(_)
205212
| Self::InvalidColumnName(_)
206213
| Self::InvalidColumnType(..)

crates/duckdb/src/statement.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,33 @@ impl Statement<'_> {
349349
self.query(params)?.get_expected_row().and_then(f)
350350
}
351351

352+
/// Convenience method to execute a query that is expected to return exactly
353+
/// one row.
354+
///
355+
/// Returns `Err(QueryReturnedMoreThanOneRow)` if the query returns more than one row.
356+
///
357+
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
358+
/// query truly is optional, you can call
359+
/// [`.optional()`](crate::OptionalExt::optional) on the result of
360+
/// this to get a `Result<Option<T>>` (requires that the trait
361+
/// `duckdb::OptionalExt` is imported).
362+
///
363+
/// # Failure
364+
///
365+
/// Will return `Err` if the underlying DuckDB call fails.
366+
pub fn query_one<T, P, F>(&mut self, params: P, f: F) -> Result<T>
367+
where
368+
P: Params,
369+
F: FnOnce(&Row<'_>) -> Result<T>,
370+
{
371+
let mut rows = self.query(params)?;
372+
let row = rows.get_expected_row().and_then(f)?;
373+
if rows.next()?.is_some() {
374+
return Err(Error::QueryReturnedMoreThanOneRow);
375+
}
376+
Ok(row)
377+
}
378+
352379
/// Return the row count
353380
#[inline]
354381
pub fn row_count(&self) -> usize {
@@ -825,6 +852,35 @@ mod test {
825852
Ok(())
826853
}
827854

855+
#[test]
856+
fn test_query_one() -> Result<()> {
857+
let db = Connection::open_in_memory()?;
858+
let sql = "BEGIN;
859+
CREATE TABLE foo(x INTEGER, y INTEGER);
860+
INSERT INTO foo VALUES(1, 3);
861+
INSERT INTO foo VALUES(2, 4);
862+
END;";
863+
db.execute_batch(sql)?;
864+
865+
// Exactly one row
866+
let y: i32 = db
867+
.prepare("SELECT y FROM foo WHERE x = ?")?
868+
.query_one([1], |r| r.get(0))?;
869+
assert_eq!(y, 3);
870+
871+
// No rows
872+
let res: Result<i32> = db
873+
.prepare("SELECT y FROM foo WHERE x = ?")?
874+
.query_one([99], |r| r.get(0));
875+
assert_eq!(res.unwrap_err(), Error::QueryReturnedNoRows);
876+
877+
// Multiple rows
878+
let res: Result<i32> = db.prepare("SELECT y FROM foo")?.query_one([], |r| r.get(0));
879+
assert_eq!(res.unwrap_err(), Error::QueryReturnedMoreThanOneRow);
880+
881+
Ok(())
882+
}
883+
828884
#[test]
829885
fn test_query_by_column_name() -> Result<()> {
830886
let db = Connection::open_in_memory()?;

0 commit comments

Comments
 (0)