Skip to content

Commit 3185514

Browse files
committed
Add query_one
1 parent 8c28f8b commit 3185514

File tree

5 files changed

+135
-2
lines changed

5 files changed

+135
-2
lines changed

postgres/src/client.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,6 @@ impl Client {
9696
/// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
9797
/// with the `prepare` method.
9898
///
99-
/// The `query_iter` method can be used to avoid buffering all rows in memory at once.
100-
///
10199
/// # Panics
102100
///
103101
/// Panics if the number of parameters provided does not match the number expected.
@@ -125,6 +123,41 @@ impl Client {
125123
executor::block_on(self.0.query(query, params))
126124
}
127125

126+
/// Executes a statement which returns a single row, returning it.
127+
///
128+
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
129+
/// provided, 1-indexed.
130+
///
131+
/// The `query` argument can either be a `Statement`, or a raw query string. If the same statement will be
132+
/// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
133+
/// with the `prepare` method.
134+
///
135+
/// # Panics
136+
///
137+
/// Panics if the number of parameters provided does not match the number expected.
138+
///
139+
/// # Examples
140+
///
141+
/// ```no_run
142+
/// use postgres::{Client, NoTls};
143+
///
144+
/// # fn main() -> Result<(), postgres::Error> {
145+
/// let mut client = Client::connect("host=localhost user=postgres", NoTls)?;
146+
///
147+
/// let baz = true;
148+
/// let row = client.query_one("SELECT foo FROM bar WHERE baz = $1", &[&baz])?;
149+
/// let foo: i32 = row.get("foo");
150+
/// println!("foo: {}", foo);
151+
/// # Ok(())
152+
/// # }
153+
/// ```
154+
pub fn query_one<T>(&mut self, query: &T, params: &[&(dyn ToSql + Sync)]) -> Result<Row, Error>
155+
where
156+
T: ?Sized + ToStatement,
157+
{
158+
executor::block_on(self.0.query_one(query, params))
159+
}
160+
128161
/// A maximally-flexible version of `query`.
129162
///
130163
/// It takes an iterator of parameters rather than a slice, and returns an iterator of rows rather than collecting

tokio-postgres/src/client.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::query::RowStream;
99
use crate::slice_iter;
1010
#[cfg(feature = "runtime")]
1111
use crate::tls::MakeTlsConnect;
12+
use pin_utils::pin_mut;
1213
use crate::tls::TlsConnect;
1314
use crate::to_statement::ToStatement;
1415
use crate::types::{Oid, ToSql, Type};
@@ -195,6 +196,13 @@ impl Client {
195196

196197
/// Executes a statement, returning a vector of the resulting rows.
197198
///
199+
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
200+
/// provided, 1-indexed.
201+
///
202+
/// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
203+
/// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
204+
/// with the `prepare` method.
205+
///
198206
/// # Panics
199207
///
200208
/// Panics if the number of parameters provided does not match the number expected.
@@ -212,8 +220,52 @@ impl Client {
212220
.await
213221
}
214222

223+
/// Executes a statement which returns a single row, returning it.
224+
///
225+
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
226+
/// provided, 1-indexed.
227+
///
228+
/// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
229+
/// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
230+
/// with the `prepare` method.
231+
///
232+
/// Returns an error if the query does not return exactly one row.
233+
///
234+
/// # Panics
235+
///
236+
/// Panics if the number of parameters provided does not match the number expected.
237+
pub async fn query_one<T>(
238+
&self,
239+
statement: &T,
240+
params: &[&(dyn ToSql + Sync)],
241+
) -> Result<Row, Error>
242+
where
243+
T: ?Sized + ToStatement
244+
{
245+
let stream = self.query_raw(statement, slice_iter(params)).await?;
246+
pin_mut!(stream);
247+
248+
let row = match stream.try_next().await? {
249+
Some(row) => row,
250+
None => return Err(Error::row_count()),
251+
};
252+
253+
if stream.try_next().await?.is_some() {
254+
return Err(Error::row_count());
255+
}
256+
257+
Ok(row)
258+
}
259+
215260
/// The maximally flexible version of [`query`].
216261
///
262+
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
263+
/// provided, 1-indexed.
264+
///
265+
/// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
266+
/// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
267+
/// with the `prepare` method.
268+
///
217269
/// # Panics
218270
///
219271
/// Panics if the number of parameters provided does not match the number expected.
@@ -231,6 +283,13 @@ impl Client {
231283

232284
/// Executes a statement, returning the number of rows modified.
233285
///
286+
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
287+
/// provided, 1-indexed.
288+
///
289+
/// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
290+
/// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
291+
/// with the `prepare` method.
292+
///
234293
/// If the statement does not modify any rows (e.g. `SELECT`), 0 is returned.
235294
///
236295
/// # Panics
@@ -249,6 +308,13 @@ impl Client {
249308

250309
/// The maximally flexible version of [`execute`].
251310
///
311+
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
312+
/// provided, 1-indexed.
313+
///
314+
/// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
315+
/// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
316+
/// with the `prepare` method.
317+
///
252318
/// # Panics
253319
///
254320
/// Panics if the number of parameters provided does not match the number expected.

tokio-postgres/src/error/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ enum Kind {
345345
Authentication,
346346
ConfigParse,
347347
Config,
348+
RowCount,
348349
#[cfg(feature = "runtime")]
349350
Connect,
350351
}
@@ -383,6 +384,7 @@ impl fmt::Display for Error {
383384
Kind::Authentication => fmt.write_str("authentication error")?,
384385
Kind::ConfigParse => fmt.write_str("invalid connection string")?,
385386
Kind::Config => fmt.write_str("invalid configuration")?,
387+
Kind::RowCount => fmt.write_str("query returned an unexpected number of rows")?,
386388
#[cfg(feature = "runtime")]
387389
Kind::Connect => fmt.write_str("error connecting to server")?,
388390
};
@@ -483,6 +485,10 @@ impl Error {
483485
Error::new(Kind::Config, Some(e))
484486
}
485487

488+
pub(crate) fn row_count() -> Error {
489+
Error::new(Kind::RowCount, None)
490+
}
491+
486492
#[cfg(feature = "runtime")]
487493
pub(crate) fn connect(e: io::Error) -> Error {
488494
Error::new(Kind::Connect, Some(Box::new(e)))

tokio-postgres/src/transaction.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ impl<'a> Transaction<'a> {
106106
self.client.query(statement, params).await
107107
}
108108

109+
/// Like `Client::query_one`.
110+
pub async fn query_one<T>(
111+
&self,
112+
statement: &T,
113+
params: &[&(dyn ToSql + Sync)],
114+
) -> Result<Row, Error>
115+
where
116+
T: ?Sized + ToStatement,
117+
{
118+
self.client.query_one(statement, params).await
119+
}
120+
109121
/// Like `Client::query_raw`.
110122
pub async fn query_raw<'b, T, I>(&self, statement: &T, params: I) -> Result<RowStream, Error>
111123
where

tokio-postgres/tests/test/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,3 +642,19 @@ async fn check_send() {
642642
is_send(&f);
643643
drop(f);
644644
}
645+
646+
#[tokio::test]
647+
async fn query_one() {
648+
let client = connect("user=postgres").await;
649+
650+
client.batch_execute("
651+
CREATE TEMPORARY TABLE foo (
652+
name TEXT
653+
);
654+
INSERT INTO foo (name) VALUES ('alice'), ('bob'), ('carol');
655+
").await.unwrap();
656+
657+
client.query_one("SELECT * FROM foo WHERE name = 'dave'", &[]).await.err().unwrap();
658+
client.query_one("SELECT * FROM foo WHERE name = 'alice'", &[]).await.unwrap();
659+
client.query_one("SELECT * FROM foo", &[]).await.err().unwrap();
660+
}

0 commit comments

Comments
 (0)