Skip to content

Commit

Permalink
query_typed tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
sfackler committed Jul 14, 2024
1 parent 257bcfd commit 71c836b
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 71 deletions.
65 changes: 65 additions & 0 deletions postgres/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,71 @@ impl Client {
Ok(RowIter::new(self.connection.as_ref(), stream))
}

/// Like `query`, but requires the types of query parameters to be explicitly specified.
///
/// Compared to `query`, this method allows performing queries without three round trips (for
/// prepare, execute, and close) by requiring the caller to specify parameter values along with
/// their Postgres type. Thus, this is suitable in environments where prepared statements aren't
/// supported (such as Cloudflare Workers with Hyperdrive).
///
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the
/// parameter of the list provided, 1-indexed.
pub fn query_typed(
&mut self,
query: &str,
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error> {
self.connection
.block_on(self.client.query_typed(query, params))
}

/// The maximally flexible version of [`query_typed`].
///
/// Compared to `query`, this method allows performing queries without three round trips (for
/// prepare, execute, and close) by requiring the caller to specify parameter values along with
/// their Postgres type. Thus, this is suitable in environments where prepared statements aren't
/// supported (such as Cloudflare Workers with Hyperdrive).
///
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the
/// parameter of the list provided, 1-indexed.
///
/// [`query_typed`]: #method.query_typed
///
/// # Examples
/// ```no_run
/// # use postgres::{Client, NoTls};
/// use postgres::types::{ToSql, Type};
/// use fallible_iterator::FallibleIterator;
/// # fn main() -> Result<(), postgres::Error> {
/// # let mut client = Client::connect("host=localhost user=postgres", NoTls)?;
///
/// let params: Vec<(String, Type)> = vec![
/// ("first param".into(), Type::TEXT),
/// ("second param".into(), Type::TEXT),
/// ];
/// let mut it = client.query_typed_raw(
/// "SELECT foo FROM bar WHERE biz = $1 AND baz = $2",
/// params,
/// )?;
///
/// while let Some(row) = it.next()? {
/// let foo: i32 = row.get("foo");
/// println!("foo: {}", foo);
/// }
/// # Ok(())
/// # }
/// ```
pub fn query_typed_raw<P, I>(&mut self, query: &str, params: I) -> Result<RowIter<'_>, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)>,
{
let stream = self
.connection
.block_on(self.client.query_typed_raw(query, params))?;
Ok(RowIter::new(self.connection.as_ref(), stream))
}

/// Creates a new prepared statement.
///
/// Prepared statements can be executed repeatedly, and may contain query parameters (indicated by `$1`, `$2`, etc),
Expand Down
45 changes: 45 additions & 0 deletions postgres/src/generic_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ pub trait GenericClient: private::Sealed {
I: IntoIterator<Item = P>,
I::IntoIter: ExactSizeIterator;

/// Like [`Client::query_typed`]
fn query_typed(
&mut self,
statement: &str,
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error>;

/// Like [`Client::query_typed_raw`]
fn query_typed_raw<P, I>(&mut self, statement: &str, params: I) -> Result<RowIter<'_>, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)> + Sync + Send;

/// Like `Client::prepare`.
fn prepare(&mut self, query: &str) -> Result<Statement, Error>;

Expand Down Expand Up @@ -115,6 +128,22 @@ impl GenericClient for Client {
self.query_raw(query, params)
}

fn query_typed(
&mut self,
statement: &str,
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error> {
self.query_typed(statement, params)
}

fn query_typed_raw<P, I>(&mut self, statement: &str, params: I) -> Result<RowIter<'_>, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)> + Sync + Send,
{
self.query_typed_raw(statement, params)
}

fn prepare(&mut self, query: &str) -> Result<Statement, Error> {
self.prepare(query)
}
Expand Down Expand Up @@ -195,6 +224,22 @@ impl GenericClient for Transaction<'_> {
self.query_raw(query, params)
}

fn query_typed(
&mut self,
statement: &str,
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error> {
self.query_typed(statement, params)
}

fn query_typed_raw<P, I>(&mut self, statement: &str, params: I) -> Result<RowIter<'_>, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)> + Sync + Send,
{
self.query_typed_raw(statement, params)
}

fn prepare(&mut self, query: &str) -> Result<Statement, Error> {
self.prepare(query)
}
Expand Down
29 changes: 29 additions & 0 deletions postgres/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,35 @@ impl<'a> Transaction<'a> {
Ok(RowIter::new(self.connection.as_ref(), stream))
}

/// Like `Client::query_typed`.
pub fn query_typed(
&mut self,
statement: &str,
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error> {
self.connection.block_on(
self.transaction
.as_ref()
.unwrap()
.query_typed(statement, params),
)
}

/// Like `Client::query_typed_raw`.
pub fn query_typed_raw<P, I>(&mut self, query: &str, params: I) -> Result<RowIter<'_>, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)>,
{
let stream = self.connection.block_on(
self.transaction
.as_ref()
.unwrap()
.query_typed_raw(query, params),
)?;
Ok(RowIter::new(self.connection.as_ref(), stream))
}

/// Binds parameters to a statement, creating a "portal".
///
/// Portals can be used with the `query_portal` method to page through the results of a query without being forced
Expand Down
63 changes: 39 additions & 24 deletions tokio-postgres/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,6 @@ impl Client {
///
/// ```no_run
/// # async fn async_main(client: &tokio_postgres::Client) -> Result<(), tokio_postgres::Error> {
/// use tokio_postgres::types::ToSql;
/// use futures_util::{pin_mut, TryStreamExt};
///
/// let params: Vec<String> = vec![
Expand Down Expand Up @@ -373,43 +372,59 @@ impl Client {
///
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the
/// parameter of the list provided, 1-indexed.
pub async fn query_typed(
&self,
query: &str,
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error> {
self.query_typed_raw(query, params.iter().map(|(v, t)| (*v, t.clone())))
.await?
.try_collect()
.await
}

/// The maximally flexible version of [`query_typed`].
///
/// Compared to `query`, this method allows performing queries without three round trips (for
/// prepare, execute, and close) by requiring the caller to specify parameter values along with
/// their Postgres type. Thus, this is suitable in environments where prepared statements aren't
/// supported (such as Cloudflare Workers with Hyperdrive).
///
/// A statement may contain parameters, specified by `$n`, where `n` is the index of the
/// parameter of the list provided, 1-indexed.
///
/// [`query_typed`]: #method.query_typed
///
/// # Examples
///
/// ```no_run
/// # async fn async_main(client: &tokio_postgres::Client) -> Result<(), tokio_postgres::Error> {
/// use tokio_postgres::types::ToSql;
/// use tokio_postgres::types::Type;
/// use futures_util::{pin_mut, TryStreamExt};
/// use tokio_postgres::types::Type;
///
/// let rows = client.query_typed(
/// let params: Vec<(String, Type)> = vec![
/// ("first param".into(), Type::TEXT),
/// ("second param".into(), Type::TEXT),
/// ];
/// let mut it = client.query_typed_raw(
/// "SELECT foo FROM bar WHERE biz = $1 AND baz = $2",
/// &[(&"first param", Type::TEXT), (&2i32, Type::INT4)],
/// params,
/// ).await?;
///
/// for row in rows {
/// let foo: i32 = row.get("foo");
/// println!("foo: {}", foo);
/// pin_mut!(it);
/// while let Some(row) = it.try_next().await? {
/// let foo: i32 = row.get("foo");
/// println!("foo: {}", foo);
/// }
/// # Ok(())
/// # }
/// ```
pub async fn query_typed(
&self,
statement: &str,
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error> {
fn slice_iter<'a>(
s: &'a [(&'a (dyn ToSql + Sync), Type)],
) -> impl ExactSizeIterator<Item = (&'a dyn ToSql, Type)> + 'a {
s.iter()
.map(|(param, param_type)| (*param as _, param_type.clone()))
}

query::query_typed(&self.inner, statement, slice_iter(params))
.await?
.try_collect()
.await
pub async fn query_typed_raw<P, I>(&self, query: &str, params: I) -> Result<RowStream, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)>,
{
query::query_typed(&self.inner, query, params).await
}

/// Executes a statement, returning the number of rows modified.
Expand Down
22 changes: 22 additions & 0 deletions tokio-postgres/src/generic_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ pub trait GenericClient: private::Sealed {
params: &[(&(dyn ToSql + Sync), Type)],
) -> Result<Vec<Row>, Error>;

/// Like [`Client::query_typed_raw`]
async fn query_typed_raw<P, I>(&self, statement: &str, params: I) -> Result<RowStream, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)> + Sync + Send;

/// Like [`Client::prepare`].
async fn prepare(&self, query: &str) -> Result<Statement, Error>;

Expand Down Expand Up @@ -154,6 +160,14 @@ impl GenericClient for Client {
self.query_typed(statement, params).await
}

async fn query_typed_raw<P, I>(&self, statement: &str, params: I) -> Result<RowStream, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)> + Sync + Send,
{
self.query_typed_raw(statement, params).await
}

async fn prepare(&self, query: &str) -> Result<Statement, Error> {
self.prepare(query).await
}
Expand Down Expand Up @@ -252,6 +266,14 @@ impl GenericClient for Transaction<'_> {
self.query_typed(statement, params).await
}

async fn query_typed_raw<P, I>(&self, statement: &str, params: I) -> Result<RowStream, Error>
where
P: BorrowToSql,
I: IntoIterator<Item = (P, Type)> + Sync + Send,
{
self.query_typed_raw(statement, params).await
}

async fn prepare(&self, query: &str) -> Result<Statement, Error> {
self.prepare(query).await
}
Expand Down
Loading

0 comments on commit 71c836b

Please sign in to comment.