Skip to main content

diesel/mysql_like/connection/stmt/
mod.rs

1#![allow(unsafe_code)] // module uses ffi
2use alloc::ffi::CString;
3use core::ffi as libc;
4use core::ffi::CStr;
5use core::marker::PhantomData;
6use core::ptr::NonNull;
7use mysqlclient_sys as ffi;
8
9use super::bind::{OutputBinds, PreparedStatementBinds};
10use super::error_information::{MysqlLikeErrorInformation, sqlstate_from_ptr};
11use crate::connection::statement_cache::{MaybeCached, PreparedStatement};
12use crate::mysql_like::{MysqlLikeBackend, MysqlType};
13use crate::result::{DatabaseErrorKind, Error, QueryResult, SqlState};
14
15pub(super) mod iterator;
16mod metadata;
17
18pub(super) use self::metadata::{MysqlFieldMetadata, StatementMetadata};
19
20#[allow(dead_code, missing_debug_implementations)]
21// https://github.com/rust-lang/rust/issues/81658
22pub struct Statement<DB: MysqlLikeBackend> {
23    stmt: NonNull<ffi::MYSQL_STMT>,
24    input_binds: Option<PreparedStatementBinds>,
25    _phantom: PhantomData<DB>,
26}
27
28impl<DB> PreparedStatement for Statement<DB>
29where
30    DB: MysqlLikeBackend,
31{
32    fn parameter_count(&self) -> u64 {
33        // SAFETY:
34        // stmt is a valid pointer to MYSQL_STMT that's not deallocated yet
35        //
36        // On windows that returns a u32
37        unsafe { ffi::mysql_stmt_param_count(self.stmt.as_ptr()) as u64 }
38    }
39}
40
41// mysql connection can be shared between threads according to libmysqlclients documentation
42#[allow(unsafe_code)]
43unsafe impl<DB: MysqlLikeBackend> Send for Statement<DB> {}
44
45impl<DB: MysqlLikeBackend> Statement<DB> {
46    pub(crate) fn new(stmt: NonNull<ffi::MYSQL_STMT>) -> Self {
47        Statement {
48            stmt,
49            input_binds: None,
50            _phantom: PhantomData,
51        }
52    }
53
54    pub fn prepare(&self, query: &str) -> QueryResult<()> {
55        use crate::result::Error::InvalidCString;
56
57        let query = CString::new(query).map_err(InvalidCString)?;
58        unsafe {
59            ffi::mysql_stmt_prepare(
60                self.stmt.as_ptr(),
61                query.as_ptr() as *const libc::c_char,
62                query.count_bytes() as libc::c_ulong,
63            );
64        }
65        self.did_an_error_occur()
66    }
67
68    pub fn bind<Iter>(&mut self, binds: Iter) -> QueryResult<()>
69    where
70        Iter: IntoIterator<Item = (MysqlType, Option<Vec<u8>>)>,
71    {
72        let input_binds = PreparedStatementBinds::from_input_data(binds);
73        self.input_bind(input_binds)
74    }
75
76    pub(super) fn input_bind(
77        &mut self,
78        mut input_binds: PreparedStatementBinds,
79    ) -> QueryResult<()> {
80        if input_binds.len() != self.parameter_count() {
81            return Err(
82                crate::connection::statement_cache::invalid_prepared_statement_parameter_count(
83                    self.parameter_count().try_into().unwrap_or(usize::MAX),
84                    input_binds.len().try_into().unwrap_or(usize::MAX),
85                ),
86            );
87        }
88
89        input_binds.with_mysql_binds(|bind_ptr| {
90            // This relies on the invariant that the current value of `self.input_binds`
91            // will not change without this function being called
92            unsafe {
93                ffi::mysql_stmt_bind_param(self.stmt.as_ptr(), bind_ptr);
94            }
95        });
96        self.input_binds = Some(input_binds);
97        self.did_an_error_occur()
98    }
99
100    fn last_error_message(&self) -> String {
101        unsafe { CStr::from_ptr(ffi::mysql_stmt_error(self.stmt.as_ptr())) }
102            .to_string_lossy()
103            .into_owned()
104    }
105
106    fn last_error_sqlstate(&self) -> Option<SqlState> {
107        // SAFETY: `self.stmt` is the live statement handle owned by `self`.
108        let sqlstate = unsafe { ffi::mysql_stmt_sqlstate(self.stmt.as_ptr()) };
109        // SAFETY: the result is null or the NUL terminated SQLSTATE stored on the
110        // statement handle, which no call changes while `self` is borrowed here.
111        unsafe { sqlstate_from_ptr(sqlstate) }
112    }
113
114    ///we return a Option here because mariadb sometimes doesn't provide the statement metadata before the execute
115    pub(super) fn metadata(&self) -> QueryResult<Option<StatementMetadata>> {
116        let result_ptr = unsafe { ffi::mysql_stmt_result_metadata(self.stmt.as_ptr()) };
117        self.did_an_error_occur()?;
118        Ok(NonNull::new(result_ptr).map(StatementMetadata::new))
119    }
120
121    pub(super) fn did_an_error_occur(&self) -> QueryResult<()> {
122        use crate::result::Error::DatabaseError;
123
124        let error_message = self.last_error_message();
125        if error_message.is_empty() {
126            Ok(())
127        } else {
128            Err(DatabaseError(
129                self.last_error_type(),
130                Box::new(MysqlLikeErrorInformation {
131                    message: error_message,
132                    sqlstate: self.last_error_sqlstate(),
133                }),
134            ))
135        }
136    }
137
138    fn last_error_type(&self) -> DatabaseErrorKind {
139        let last_error_number = unsafe { ffi::mysql_stmt_errno(self.stmt.as_ptr()) };
140
141        DB::map_error_number(last_error_number)
142    }
143
144    /// If the pointers referenced by the `MYSQL_BIND` structures are invalidated,
145    /// you must call this function again before calling `mysql_stmt_fetch`.
146    pub unsafe fn bind_result(&self, binds: *mut ffi::MYSQL_BIND) -> QueryResult<()> {
147        unsafe {
148            ffi::mysql_stmt_bind_result(self.stmt.as_ptr(), binds);
149        }
150        self.did_an_error_occur()
151    }
152}
153
154impl<'a, DB: MysqlLikeBackend> MaybeCached<'a, Statement<DB>> {
155    pub(super) fn execute_statement(
156        self,
157        binds: &mut OutputBinds,
158    ) -> QueryResult<StatementUse<'a, DB>> {
159        unsafe {
160            binds.with_mysql_binds(|bind_ptr| self.bind_result(bind_ptr))?;
161            self.execute()
162        }
163    }
164
165    /// This function should be called instead of `results` on queries which
166    /// have no return value. It should never be called on a statement on
167    /// which `results` has previously been called?
168    pub(super) unsafe fn execute(self) -> QueryResult<StatementUse<'a, DB>> {
169        unsafe {
170            ffi::mysql_stmt_execute(self.stmt.as_ptr());
171        }
172        self.did_an_error_occur()?;
173        unsafe {
174            ffi::mysql_stmt_store_result(self.stmt.as_ptr());
175        }
176        let ret = StatementUse { inner: self };
177        ret.inner.did_an_error_occur()?;
178        Ok(ret)
179    }
180}
181
182impl<DB: MysqlLikeBackend> Drop for Statement<DB> {
183    fn drop(&mut self) {
184        unsafe { ffi::mysql_stmt_close(self.stmt.as_ptr()) };
185    }
186}
187
188#[allow(missing_debug_implementations)]
189pub(super) struct StatementUse<'a, DB: MysqlLikeBackend> {
190    inner: MaybeCached<'a, Statement<DB>>,
191}
192
193impl<DB: MysqlLikeBackend> StatementUse<'_, DB> {
194    pub(in crate::mysql_like::connection) fn affected_rows(&self) -> QueryResult<usize> {
195        let affected_rows = unsafe { ffi::mysql_stmt_affected_rows(self.inner.stmt.as_ptr()) };
196        affected_rows
197            .try_into()
198            .map_err(|e| Error::DeserializationError(Box::new(e)))
199    }
200
201    /// The executed statement's `mysql_stmt_insert_id`, zero when it set no
202    /// `AUTO_INCREMENT` value.
203    ///
204    /// The C API calls that zero "undefined", meaning an unspecified value and
205    /// not undefined behaviour: only a statement returning a result set can
206    /// inherit a stale id from the connection.
207    pub(in crate::mysql_like::connection) fn insert_id(&self) -> u64 {
208        // SAFETY: `self.inner` holds a live `MYSQL_STMT` and this reads an
209        // initialised scalar field.
210        unsafe { ffi::mysql_stmt_insert_id(self.inner.stmt.as_ptr()) }
211    }
212
213    /// This function should be called after `execute` only
214    /// otherwise it's not guaranteed to return a valid result
215    pub(in crate::mysql_like::connection) unsafe fn result_size(&mut self) -> QueryResult<usize> {
216        let size = unsafe { ffi::mysql_stmt_num_rows(self.inner.stmt.as_ptr()) };
217        usize::try_from(size).map_err(|e| Error::DeserializationError(Box::new(e)))
218    }
219
220    pub(super) fn populate_row_buffers(&self, binds: &mut OutputBinds) -> QueryResult<Option<()>> {
221        // We're about to call `mysql_stmt_fetch` we need to check if our binds are still valid
222        if binds.are_invalid() {
223            binds.bind_results(self)?;
224        }
225        let next_row_result = unsafe { ffi::mysql_stmt_fetch(self.inner.stmt.as_ptr()) };
226        if next_row_result < 0 {
227            self.inner.did_an_error_occur().map(Some)
228        } else {
229            #[allow(clippy::cast_sign_loss)] // that's how it's supposed to be based on the API
230            match next_row_result as libc::c_uint {
231                ffi::MYSQL_NO_DATA => Ok(None),
232                ffi::MYSQL_DATA_TRUNCATED => binds.populate_dynamic_buffers(self).map(Some),
233                0 => {
234                    binds.update_buffer_lengths();
235                    Ok(Some(()))
236                }
237                _error => self.inner.did_an_error_occur().map(Some),
238            }
239        }
240    }
241
242    pub(in crate::mysql_like::connection) unsafe fn fetch_column(
243        &self,
244        bind: &mut ffi::MYSQL_BIND,
245        idx: usize,
246        offset: usize,
247    ) -> QueryResult<()> {
248        unsafe {
249            ffi::mysql_stmt_fetch_column(
250                self.inner.stmt.as_ptr(),
251                bind,
252                idx.try_into()
253                    .map_err(|e| Error::DeserializationError(Box::new(e)))?,
254                offset as libc::c_ulong,
255            );
256        }
257        self.inner.did_an_error_occur()
258    }
259
260    /// If the pointers referenced by the `MYSQL_BIND` structures are invalidated,
261    /// you must call this function again before calling `mysql_stmt_fetch`.
262    pub(in crate::mysql_like::connection) unsafe fn bind_result(
263        &self,
264        binds: *mut ffi::MYSQL_BIND,
265    ) -> QueryResult<()> {
266        unsafe { self.inner.bind_result(binds) }
267    }
268
269    pub(super) fn metadata(&self) -> QueryResult<StatementMetadata> {
270        use crate::result::Error::DeserializationError;
271
272        //At this stage the statement was executed and should therefore provide its metadata
273        self.inner
274            .metadata()?
275            .ok_or_else(|| DeserializationError("No metadata exists".into()))
276    }
277}
278
279impl<DB: MysqlLikeBackend> Drop for StatementUse<'_, DB> {
280    fn drop(&mut self) {
281        unsafe {
282            ffi::mysql_stmt_free_result(self.inner.stmt.as_ptr());
283        }
284    }
285}