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