Skip to main content

diesel/mysql_like/connection/stmt/
mod.rs

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