Skip to main content

tokio_postgres/
query.rs

1use crate::client::{InnerClient, Responses};
2use crate::codec::FrontendMessage;
3use crate::connection::RequestMessages;
4use crate::prepare::get_type;
5use crate::types::{BorrowToSql, IsNull};
6use crate::{Column, Error, Portal, Row, Statement};
7use bytes::{Bytes, BytesMut};
8use fallible_iterator::FallibleIterator;
9use futures_util::{ready, Stream};
10use log::{debug, log_enabled, Level};
11use pin_project_lite::pin_project;
12use postgres_protocol::message::backend::{CommandCompleteBody, Message};
13use postgres_protocol::message::frontend;
14use postgres_types::Type;
15use std::fmt;
16use std::marker::PhantomPinned;
17use std::pin::Pin;
18use std::sync::Arc;
19use std::task::{Context, Poll};
20
21/// The format used to encode result column values in the PostgreSQL wire protocol.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ResultFormat {
24    /// Text format — values are returned as UTF-8 strings,
25    /// the same format the pg Node.js library receives.
26    Text,
27    /// Binary format — values are returned in PostgreSQL's binary encoding.
28    /// This is more efficient but requires type-aware deserialization.
29    Binary,
30}
31
32impl ResultFormat {
33    fn format_code(self) -> i16 {
34        match self {
35            ResultFormat::Text => 0,
36            ResultFormat::Binary => 1,
37        }
38    }
39}
40
41struct BorrowToSqlParamsDebug<'a, T>(&'a [T]);
42
43impl<T> fmt::Debug for BorrowToSqlParamsDebug<'_, T>
44where
45    T: BorrowToSql,
46{
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        f.debug_list()
49            .entries(self.0.iter().map(|x| x.borrow_to_sql()))
50            .finish()
51    }
52}
53
54pub async fn query<P, I>(
55    client: &InnerClient,
56    statement: Statement,
57    params: I,
58) -> Result<RowStream, Error>
59where
60    P: BorrowToSql,
61    I: IntoIterator<Item = P>,
62    I::IntoIter: ExactSizeIterator,
63{
64    query_with_format(client, statement, params, ResultFormat::Binary).await
65}
66
67pub async fn query_with_format<P, I>(
68    client: &InnerClient,
69    statement: Statement,
70    params: I,
71    result_format: ResultFormat,
72) -> Result<RowStream, Error>
73where
74    P: BorrowToSql,
75    I: IntoIterator<Item = P>,
76    I::IntoIter: ExactSizeIterator,
77{
78    let buf = if log_enabled!(Level::Debug) {
79        let params = params.into_iter().collect::<Vec<_>>();
80        debug!(
81            "executing statement {} with parameters: {:?}",
82            statement.name(),
83            BorrowToSqlParamsDebug(params.as_slice()),
84        );
85        encode(client, &statement, params, result_format)?
86    } else {
87        encode(client, &statement, params, result_format)?
88    };
89    let responses = start(client, buf).await?;
90    Ok(RowStream {
91        statement,
92        responses,
93        rows_affected: None,
94        command_tag: None,
95        _p: PhantomPinned,
96    })
97}
98
99pub async fn query_typed<P, I>(
100    client: &Arc<InnerClient>,
101    query: &str,
102    params: I,
103) -> Result<RowStream, Error>
104where
105    P: BorrowToSql,
106    I: IntoIterator<Item = (P, Type)>,
107{
108    let buf = {
109        let params = params.into_iter().collect::<Vec<_>>();
110        let param_oids = params.iter().map(|(_, t)| t.oid()).collect::<Vec<_>>();
111
112        client.with_buf(|buf| {
113            frontend::parse("", query, param_oids, buf).map_err(Error::parse)?;
114            encode_bind_raw("", params, "", ResultFormat::Binary, buf)?;
115            frontend::describe(b'S', "", buf).map_err(Error::encode)?;
116            frontend::execute("", 0, buf).map_err(Error::encode)?;
117            frontend::sync(buf);
118
119            Ok(buf.split().freeze())
120        })?
121    };
122
123    let mut responses = client.send(RequestMessages::Single(FrontendMessage::Raw(buf)))?;
124
125    loop {
126        match responses.next().await? {
127            Message::ParseComplete | Message::BindComplete | Message::ParameterDescription(_) => {}
128            Message::NoData => {
129                return Ok(RowStream {
130                    statement: Statement::unnamed(vec![], vec![]),
131                    responses,
132                    rows_affected: None,
133                    command_tag: None,
134                    _p: PhantomPinned,
135                });
136            }
137            Message::RowDescription(row_description) => {
138                let mut columns: Vec<Column> = vec![];
139                let mut it = row_description.fields();
140                while let Some(field) = it.next().map_err(Error::parse)? {
141                    let type_ = get_type(client, field.type_oid()).await?;
142                    let column = Column {
143                        name: field.name().to_string(),
144                        table_oid: Some(field.table_oid()).filter(|n| *n != 0),
145                        column_id: Some(field.column_id()).filter(|n| *n != 0),
146                        r#type: type_,
147                    };
148                    columns.push(column);
149                }
150                return Ok(RowStream {
151                    statement: Statement::unnamed(vec![], columns),
152                    responses,
153                    rows_affected: None,
154                    command_tag: None,
155                    _p: PhantomPinned,
156                });
157            }
158            _ => return Err(Error::unexpected_message()),
159        }
160    }
161}
162
163pub async fn query_portal(
164    client: &InnerClient,
165    portal: &Portal,
166    max_rows: i32,
167) -> Result<RowStream, Error> {
168    let buf = client.with_buf(|buf| {
169        frontend::execute(portal.name(), max_rows, buf).map_err(Error::encode)?;
170        frontend::sync(buf);
171        Ok(buf.split().freeze())
172    })?;
173
174    let responses = client.send(RequestMessages::Single(FrontendMessage::Raw(buf)))?;
175
176    Ok(RowStream {
177        statement: portal.statement().clone(),
178        responses,
179        rows_affected: None,
180        command_tag: None,
181        _p: PhantomPinned,
182    })
183}
184
185/// Extract the number of rows affected from [`CommandCompleteBody`].
186pub fn extract_row_affected(body: &CommandCompleteBody) -> Result<u64, Error> {
187    let rows = body
188        .tag()
189        .map_err(Error::parse)?
190        .rsplit(' ')
191        .next()
192        .unwrap()
193        .parse()
194        .unwrap_or(0);
195    Ok(rows)
196}
197
198pub async fn execute<P, I>(
199    client: &InnerClient,
200    statement: Statement,
201    params: I,
202) -> Result<u64, Error>
203where
204    P: BorrowToSql,
205    I: IntoIterator<Item = P>,
206    I::IntoIter: ExactSizeIterator,
207{
208    let buf = if log_enabled!(Level::Debug) {
209        let params = params.into_iter().collect::<Vec<_>>();
210        debug!(
211            "executing statement {} with parameters: {:?}",
212            statement.name(),
213            BorrowToSqlParamsDebug(params.as_slice()),
214        );
215        encode(client, &statement, params, ResultFormat::Binary)?
216    } else {
217        encode(client, &statement, params, ResultFormat::Binary)?
218    };
219    let mut responses = start(client, buf).await?;
220
221    let mut rows = 0;
222    loop {
223        match responses.next().await? {
224            Message::DataRow(_) => {}
225            Message::CommandComplete(body) => {
226                rows = extract_row_affected(&body)?;
227            }
228            Message::EmptyQueryResponse => rows = 0,
229            Message::ReadyForQuery(_) => return Ok(rows),
230            _ => return Err(Error::unexpected_message()),
231        }
232    }
233}
234
235async fn start(client: &InnerClient, buf: Bytes) -> Result<Responses, Error> {
236    let mut responses = client.send(RequestMessages::Single(FrontendMessage::Raw(buf)))?;
237
238    match responses.next().await? {
239        Message::BindComplete => {}
240        _ => return Err(Error::unexpected_message()),
241    }
242
243    Ok(responses)
244}
245
246pub fn encode<P, I>(
247    client: &InnerClient,
248    statement: &Statement,
249    params: I,
250    result_format: ResultFormat,
251) -> Result<Bytes, Error>
252where
253    P: BorrowToSql,
254    I: IntoIterator<Item = P>,
255    I::IntoIter: ExactSizeIterator,
256{
257    client.with_buf(|buf| {
258        encode_bind(statement, params, "", result_format, buf)?;
259        frontend::execute("", 0, buf).map_err(Error::encode)?;
260        frontend::sync(buf);
261        Ok(buf.split().freeze())
262    })
263}
264
265pub fn encode_bind<P, I>(
266    statement: &Statement,
267    params: I,
268    portal: &str,
269    result_format: ResultFormat,
270    buf: &mut BytesMut,
271) -> Result<(), Error>
272where
273    P: BorrowToSql,
274    I: IntoIterator<Item = P>,
275    I::IntoIter: ExactSizeIterator,
276{
277    let params = params.into_iter();
278    if params.len() != statement.params().len() {
279        return Err(Error::parameters(params.len(), statement.params().len()));
280    }
281
282    encode_bind_raw(
283        statement.name(),
284        params.zip(statement.params().iter().cloned()),
285        portal,
286        result_format,
287        buf,
288    )
289}
290
291fn encode_bind_raw<P, I>(
292    statement_name: &str,
293    params: I,
294    portal: &str,
295    result_format: ResultFormat,
296    buf: &mut BytesMut,
297) -> Result<(), Error>
298where
299    P: BorrowToSql,
300    I: IntoIterator<Item = (P, Type)>,
301    I::IntoIter: ExactSizeIterator,
302{
303    let (param_formats, params): (Vec<_>, Vec<_>) = params
304        .into_iter()
305        .map(|(p, ty)| (p.borrow_to_sql().encode_format(&ty) as i16, (p, ty)))
306        .unzip();
307
308    let mut error_idx = 0;
309    let r = frontend::bind(
310        portal,
311        statement_name,
312        param_formats,
313        params.into_iter().enumerate(),
314        |(idx, (param, ty)), buf| match param.borrow_to_sql().to_sql_checked(&ty, buf) {
315            Ok(IsNull::No) => Ok(postgres_protocol::IsNull::No),
316            Ok(IsNull::Yes) => Ok(postgres_protocol::IsNull::Yes),
317            Err(e) => {
318                error_idx = idx;
319                Err(e)
320            }
321        },
322        Some(result_format.format_code()),
323        buf,
324    );
325    match r {
326        Ok(()) => Ok(()),
327        Err(frontend::BindError::Conversion(e)) => Err(Error::to_sql(e, error_idx)),
328        Err(frontend::BindError::Serialization(e)) => Err(Error::encode(e)),
329    }
330}
331
332pin_project! {
333    /// A stream of table rows.
334    pub struct RowStream {
335        statement: Statement,
336        responses: Responses,
337        rows_affected: Option<u64>,
338        command_tag: Option<String>,
339        #[pin]
340        _p: PhantomPinned,
341    }
342}
343
344impl Stream for RowStream {
345    type Item = Result<Row, Error>;
346
347    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
348        let this = self.project();
349        loop {
350            match ready!(this.responses.poll_next(cx)?) {
351                Message::DataRow(body) => {
352                    return Poll::Ready(Some(Ok(Row::new(this.statement.clone(), body)?)))
353                }
354                Message::CommandComplete(body) => {
355                    *this.rows_affected = Some(extract_row_affected(&body)?);
356                    *this.command_tag = extract_command_tag(&body).ok();
357                }
358                Message::EmptyQueryResponse | Message::PortalSuspended => {}
359                Message::ReadyForQuery(_) => return Poll::Ready(None),
360                _ => return Poll::Ready(Some(Err(Error::unexpected_message()))),
361            }
362        }
363    }
364}
365
366/// Extract the command name (e.g. "SELECT", "INSERT") from a command complete tag.
367fn extract_command_tag(body: &CommandCompleteBody) -> Result<String, Error> {
368    let tag = body.tag().map_err(Error::parse)?;
369    // Tags look like "SELECT 5", "INSERT 0 1", "UPDATE 3", "DELETE 2", "CREATE TABLE"
370    // The command is everything before the last space-separated number(s).
371    Ok(tag
372        .split(' ')
373        .take_while(|part| part.parse::<u64>().is_err())
374        .collect::<Vec<_>>()
375        .join(" "))
376}
377
378impl RowStream {
379    /// Returns information about the columns of data in the rows.
380    pub fn columns(&self) -> &[Column] {
381        self.statement.columns()
382    }
383
384    /// Returns the number of rows affected by the query.
385    ///
386    /// This function will return `None` until the stream has been exhausted.
387    pub fn rows_affected(&self) -> Option<u64> {
388        self.rows_affected
389    }
390
391    /// Returns the command tag from the query (e.g. "SELECT", "INSERT", "UPDATE").
392    ///
393    /// This function will return `None` until the stream has been exhausted.
394    pub fn command_tag(&self) -> Option<&str> {
395        self.command_tag.as_deref()
396    }
397}