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    pub(super) fn metadata(&self) -> QueryResult<StatementMetadata> {
80        use crate::result::Error::DeserializationError;
81
82        let result_ptr = unsafe { ffi::mysql_stmt_result_metadata(self.stmt.as_ptr()) };
83        self.did_an_error_occur()?;
84        NonNull::new(result_ptr)
85            .map(StatementMetadata::new)
86            .ok_or_else(|| DeserializationError("No metadata exists".into()))
87    }
88
89    pub(super) fn did_an_error_occur(&self) -> QueryResult<()> {
90        use crate::result::Error::DatabaseError;
91
92        let error_message = self.last_error_message();
93        if error_message.is_empty() {
94            Ok(())
95        } else {
96            Err(DatabaseError(
97                self.last_error_type(),
98                Box::new(error_message),
99            ))
100        }
101    }
102
103    fn last_error_type(&self) -> DatabaseErrorKind {
104        let last_error_number = unsafe { ffi::mysql_stmt_errno(self.stmt.as_ptr()) };
105
106        DB::map_error_number(last_error_number)
107    }
108
109    /// If the pointers referenced by the `MYSQL_BIND` structures are invalidated,
110    /// you must call this function again before calling `mysql_stmt_fetch`.
111    pub unsafe fn bind_result(&self, binds: *mut ffi::MYSQL_BIND) -> QueryResult<()> {
112        unsafe {
113            ffi::mysql_stmt_bind_result(self.stmt.as_ptr(), binds);
114        }
115        self.did_an_error_occur()
116    }
117}
118
119impl<'a, DB: MysqlLikeBackend> MaybeCached<'a, Statement<DB>> {
120    pub(super) fn execute_statement(
121        self,
122        binds: &mut OutputBinds,
123    ) -> QueryResult<StatementUse<'a, DB>> {
124        unsafe {
125            binds.with_mysql_binds(|bind_ptr| self.bind_result(bind_ptr))?;
126            self.execute()
127        }
128    }
129
130    /// This function should be called instead of `results` on queries which
131    /// have no return value. It should never be called on a statement on
132    /// which `results` has previously been called?
133    pub(super) unsafe fn execute(self) -> QueryResult<StatementUse<'a, DB>> {
134        unsafe {
135            ffi::mysql_stmt_execute(self.stmt.as_ptr());
136        }
137        self.did_an_error_occur()?;
138        unsafe {
139            ffi::mysql_stmt_store_result(self.stmt.as_ptr());
140        }
141        let ret = StatementUse { inner: self };
142        ret.inner.did_an_error_occur()?;
143        Ok(ret)
144    }
145}
146
147impl<DB: MysqlLikeBackend> Drop for Statement<DB> {
148    fn drop(&mut self) {
149        unsafe { ffi::mysql_stmt_close(self.stmt.as_ptr()) };
150    }
151}
152
153#[allow(missing_debug_implementations)]
154pub(super) struct StatementUse<'a, DB: MysqlLikeBackend> {
155    inner: MaybeCached<'a, Statement<DB>>,
156}
157
158impl<DB: MysqlLikeBackend> StatementUse<'_, DB> {
159    pub(in crate::mysql_like::connection) fn affected_rows(&self) -> QueryResult<usize> {
160        let affected_rows = unsafe { ffi::mysql_stmt_affected_rows(self.inner.stmt.as_ptr()) };
161        affected_rows
162            .try_into()
163            .map_err(|e| Error::DeserializationError(Box::new(e)))
164    }
165
166    /// This function should be called after `execute` only
167    /// otherwise it's not guaranteed to return a valid result
168    pub(in crate::mysql_like::connection) unsafe fn result_size(&mut self) -> QueryResult<usize> {
169        let size = unsafe { ffi::mysql_stmt_num_rows(self.inner.stmt.as_ptr()) };
170        usize::try_from(size).map_err(|e| Error::DeserializationError(Box::new(e)))
171    }
172
173    pub(super) fn populate_row_buffers(&self, binds: &mut OutputBinds) -> QueryResult<Option<()>> {
174        // We're about to call `mysql_stmt_fetch` we need to check if our binds are still valid
175        if binds.are_invalid() {
176            binds.bind_results(self)?;
177        }
178        let next_row_result = unsafe { ffi::mysql_stmt_fetch(self.inner.stmt.as_ptr()) };
179        if next_row_result < 0 {
180            self.inner.did_an_error_occur().map(Some)
181        } else {
182            #[allow(clippy::cast_sign_loss)] // that's how it's supposed to be based on the API
183            match next_row_result as libc::c_uint {
184                ffi::MYSQL_NO_DATA => Ok(None),
185                ffi::MYSQL_DATA_TRUNCATED => binds.populate_dynamic_buffers(self).map(Some),
186                0 => {
187                    binds.update_buffer_lengths();
188                    Ok(Some(()))
189                }
190                _error => self.inner.did_an_error_occur().map(Some),
191            }
192        }
193    }
194
195    pub(in crate::mysql_like::connection) unsafe fn fetch_column(
196        &self,
197        bind: &mut ffi::MYSQL_BIND,
198        idx: usize,
199        offset: usize,
200    ) -> QueryResult<()> {
201        unsafe {
202            ffi::mysql_stmt_fetch_column(
203                self.inner.stmt.as_ptr(),
204                bind,
205                idx.try_into()
206                    .map_err(|e| Error::DeserializationError(Box::new(e)))?,
207                offset as libc::c_ulong,
208            );
209        }
210        self.inner.did_an_error_occur()
211    }
212
213    /// If the pointers referenced by the `MYSQL_BIND` structures are invalidated,
214    /// you must call this function again before calling `mysql_stmt_fetch`.
215    pub(in crate::mysql_like::connection) unsafe fn bind_result(
216        &self,
217        binds: *mut ffi::MYSQL_BIND,
218    ) -> QueryResult<()> {
219        unsafe { self.inner.bind_result(binds) }
220    }
221}
222
223impl<DB: MysqlLikeBackend> Drop for StatementUse<'_, DB> {
224    fn drop(&mut self) {
225        unsafe {
226            ffi::mysql_stmt_free_result(self.inner.stmt.as_ptr());
227        }
228    }
229}