Skip to main content

diesel/pg/connection/stmt/
mod.rs

1#![allow(unsafe_code)] // ffi code
2extern crate pq_sys;
3
4use alloc::ffi::CString;
5use core::ffi as libc;
6use core::ptr;
7
8use super::result::PgResult;
9use super::statement_cache::PrepareForCache;
10use crate::connection::statement_cache::PreparedStatement;
11use crate::pg::PgTypeMetadata;
12use crate::result::QueryResult;
13
14use super::raw::RawConnection;
15
16enum StatementKind {
17    Unnamed { sql: CString, param_types: Vec<u32> },
18    Named { name: CString },
19}
20
21pub(crate) struct Statement {
22    kind: StatementKind,
23    param_formats: Vec<libc::c_int>,
24}
25
26impl PreparedStatement for Statement {
27    fn parameter_count(&self) -> u64 {
28        self.param_formats.len() as u64
29    }
30}
31
32impl Statement {
33    pub(super) fn execute(
34        &self,
35        raw_connection: &mut RawConnection,
36        param_data: &[Option<Vec<u8>>],
37        row_by_row: bool,
38        disallow_copy: bool,
39    ) -> QueryResult<PgResult> {
40        // This check may seem redundant to the general check in the statement cache
41        // but it's mostly here as in depth defence to prevent libpq
42        // performing an out of bounds memory access for direct calls
43        if self.param_formats.len() != param_data.len() {
44            return Err(
45                crate::connection::statement_cache::invalid_prepared_statement_parameter_count(
46                    self.param_formats.len(),
47                    param_data.len(),
48                ),
49            );
50        }
51
52        let params_pointer = param_data
53            .iter()
54            .map(|data| {
55                data.as_ref()
56                    .map(|d| d.as_ptr() as *const libc::c_char)
57                    .unwrap_or(ptr::null())
58            })
59            .collect::<Vec<_>>();
60        let param_lengths = param_data
61            .iter()
62            .map(|data| data.as_ref().map(|d| d.len().try_into()).unwrap_or(Ok(0)))
63            .collect::<Result<Vec<_>, _>>()
64            .map_err(|_: core::num::TryFromIntError| {
65                crate::result::Error::SerializationError(
66                    "A bind parameter's serialized size is bigger than fits on an i32".into(),
67                )
68            })?;
69        let param_count: libc::c_int =
70            params_pointer
71                .len()
72                .try_into()
73                .map_err(|_: core::num::TryFromIntError| {
74                    crate::result::Error::SerializationError(
75                        "There are more than i32::MAX bind parameters".into(),
76                    )
77                })?;
78
79        match &self.kind {
80            StatementKind::Named { name } => {
81                unsafe {
82                    // execute the previously prepared statement
83                    // in autocommit mode, this will be a new transaction
84                    raw_connection.send_query_prepared(
85                        name.as_ptr(),
86                        param_count,
87                        params_pointer.as_ptr(),
88                        param_lengths.as_ptr(),
89                        self.param_formats.as_ptr(),
90                        1,
91                    )
92                }?
93            }
94            StatementKind::Unnamed { sql, param_types } => unsafe {
95                // execute the unnamed prepared statement using send_query_params
96                // which internally calls PQsendQueryParams, making sure the
97                // prepare and execute happens in a single transaction. This
98                // makes sure these are handled by PgBouncer.
99                // See https://github.com/diesel-rs/diesel/pull/4539
100                raw_connection.send_query_params(
101                    sql.as_ptr(),
102                    param_count,
103                    param_types.as_ptr(),
104                    params_pointer.as_ptr(),
105                    param_lengths.as_ptr(),
106                    self.param_formats.as_ptr(),
107                    1,
108                )
109            }?,
110        };
111
112        if row_by_row {
113            raw_connection.enable_row_by_row_mode()?;
114        }
115        let result = raw_connection.get_next_result()?.expect("Is never none");
116        handle_copy_in_normal_stmt(raw_connection, disallow_copy, result)
117    }
118
119    pub(super) fn prepare(
120        raw_connection: &mut RawConnection,
121        sql: &str,
122        is_cached: PrepareForCache,
123        param_types: &[PgTypeMetadata],
124    ) -> QueryResult<Self> {
125        let sql_cstr = CString::new(sql)?;
126        let param_types_vec = param_types
127            .iter()
128            .map(|x| x.oid())
129            .collect::<Result<Vec<_>, _>>()
130            .map_err(|e| crate::result::Error::SerializationError(Box::new(e)))?;
131
132        match is_cached {
133            PrepareForCache::Yes { counter } => {
134                // For named/cached statements, prepare as usual using a prepare phase and then
135                // an execute phase
136                let name_cstr = CString::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__diesel_stmt_{0}", counter))
    })format!("__diesel_stmt_{counter}"))?;
137                let internal_result =
138                    unsafe {
139                        let param_count: libc::c_int = param_types.len().try_into().map_err(
140                            |_: core::num::TryFromIntError| {
141                                crate::result::Error::SerializationError(
142                                    "There are more than i32::MAX bind parameters".into(),
143                                )
144                            },
145                        )?;
146                        raw_connection.prepare(
147                            name_cstr.as_ptr(),
148                            sql_cstr.as_ptr(),
149                            param_count,
150                            param_types_vec.as_ptr(),
151                        )
152                    };
153                PgResult::new(internal_result?, raw_connection)?;
154
155                Ok(Statement {
156                    kind: StatementKind::Named { name: name_cstr },
157                    param_formats: ::alloc::vec::from_elem_global(1, param_types.len())vec![1; param_types.len()],
158                })
159            }
160            PrepareForCache::No => {
161                // For unnamed statements, we'll return a Statement object without
162                // actually preparing it. This allows us to use send_query_params
163                // later in the execute call. This is needed to better interface
164                // with PgBouncer which cannot handle unnamed prepared statements
165                // when those are prepared and executed in separate transactions.
166                // See https://github.com/diesel-rs/diesel/pull/4539
167                Ok(Statement {
168                    kind: StatementKind::Unnamed {
169                        sql: sql_cstr,
170                        param_types: param_types_vec,
171                    },
172                    param_formats: ::alloc::vec::from_elem_global(1, param_types.len())vec![1; param_types.len()],
173                })
174            }
175        }
176    }
177}
178
179pub(super) fn handle_copy_in_normal_stmt(
180    raw_connection: &mut RawConnection,
181    disallow_copy: bool,
182    result: PgResult,
183) -> QueryResult<PgResult> {
184    if disallow_copy
185        && #[allow(non_exhaustive_omitted_patterns)] match result.result_status() {
    pq_sys::ExecStatusType::PGRES_COPY_IN |
        pq_sys::ExecStatusType::PGRES_COPY_OUT |
        pq_sys::ExecStatusType::PGRES_COPY_BOTH => true,
    _ => false,
}matches!(
186            result.result_status(),
187            pq_sys::ExecStatusType::PGRES_COPY_IN
188                | pq_sys::ExecStatusType::PGRES_COPY_OUT
189                | pq_sys::ExecStatusType::PGRES_COPY_BOTH
190        )
191    {
192        raw_connection.consume_results()?;
193    }
194    Ok(result)
195}