diesel/mysql/connection/stmt/
mod.rs

1#![allow(unsafe_code)] // module uses ffi
2use mysqlclient_sys as ffi;
3use std::ffi::CStr;
4use std::os::raw as libc;
5use std::ptr::NonNull;
6
7use super::bind::{OutputBinds, PreparedStatementBinds};
8use crate::connection::statement_cache::MaybeCached;
9use crate::mysql::MysqlType;
10use crate::result::{DatabaseErrorKind, Error, QueryResult};
11
12pub(super) mod iterator;
13mod metadata;
14
15pub(super) use self::metadata::{MysqlFieldMetadata, StatementMetadata};
16
17#[allow(dead_code, missing_debug_implementations)]
18// https://github.com/rust-lang/rust/issues/81658
19pub struct Statement {
20    stmt: NonNull<ffi::MYSQL_STMT>,
21    input_binds: Option<PreparedStatementBinds>,
22}
23
24// mysql connection can be shared between threads according to libmysqlclients documentation
25#[allow(unsafe_code)]
26unsafe impl Send for Statement {}
27
28impl Statement {
29    pub(crate) fn new(stmt: NonNull<ffi::MYSQL_STMT>) -> Self {
30        Statement {
31            stmt,
32            input_binds: None,
33        }
34    }
35
36    pub fn prepare(&self, query: &str) -> QueryResult<()> {
37        unsafe {
38            ffi::mysql_stmt_prepare(
39                self.stmt.as_ptr(),
40                query.as_ptr() as *const libc::c_char,
41                query.len() as libc::c_ulong,
42            );
43        }
44        self.did_an_error_occur()
45    }
46
47    pub fn bind<Iter>(&mut self, binds: Iter) -> QueryResult<()>
48    where
49        Iter: IntoIterator<Item = (MysqlType, Option<Vec<u8>>)>,
50    {
51        let input_binds = PreparedStatementBinds::from_input_data(binds);
52        self.input_bind(input_binds)
53    }
54
55    pub(super) fn input_bind(
56        &mut self,
57        mut input_binds: PreparedStatementBinds,
58    ) -> QueryResult<()> {
59        input_binds.with_mysql_binds(|bind_ptr| {
60            // This relies on the invariant that the current value of `self.input_binds`
61            // will not change without this function being called
62            unsafe {
63                ffi::mysql_stmt_bind_param(self.stmt.as_ptr(), bind_ptr);
64            }
65        });
66        self.input_binds = Some(input_binds);
67        self.did_an_error_occur()
68    }
69
70    fn last_error_message(&self) -> String {
71        unsafe { CStr::from_ptr(ffi::mysql_stmt_error(self.stmt.as_ptr())) }
72            .to_string_lossy()
73            .into_owned()
74    }
75
76    pub(super) fn metadata(&self) -> QueryResult<StatementMetadata> {
77        use crate::result::Error::DeserializationError;
78
79        let result_ptr = unsafe { ffi::mysql_stmt_result_metadata(self.stmt.as_ptr()) };
80        self.did_an_error_occur()?;
81        NonNull::new(result_ptr)
82            .map(StatementMetadata::new)
83            .ok_or_else(|| DeserializationError("No metadata exists".into()))
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        // These values are not exposed by the C API, but are documented
103        // at https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html
104        // and are from the ANSI SQLSTATE standard
105        match last_error_number {
106            1062 | 1586 | 1859 => DatabaseErrorKind::UniqueViolation,
107            1216 | 1217 | 1451 | 1452 | 1830 | 1834 => DatabaseErrorKind::ForeignKeyViolation,
108            1792 => DatabaseErrorKind::ReadOnlyTransaction,
109            1048 | 1364 => DatabaseErrorKind::NotNullViolation,
110            3819 => DatabaseErrorKind::CheckViolation,
111            1213 => DatabaseErrorKind::SerializationFailure,
112            _ => DatabaseErrorKind::Unknown,
113        }
114    }
115
116    /// If the pointers referenced by the `MYSQL_BIND` structures are invalidated,
117    /// you must call this function again before calling `mysql_stmt_fetch`.
118    pub unsafe fn bind_result(&self, binds: *mut ffi::MYSQL_BIND) -> QueryResult<()> {
119        unsafe {
120            ffi::mysql_stmt_bind_result(self.stmt.as_ptr(), binds);
121        }
122        self.did_an_error_occur()
123    }
124}
125
126impl<'a> MaybeCached<'a, Statement> {
127    pub(super) fn execute_statement(
128        self,
129        binds: &mut OutputBinds,
130    ) -> QueryResult<StatementUse<'a>> {
131        unsafe {
132            binds.with_mysql_binds(|bind_ptr| self.bind_result(bind_ptr))?;
133            self.execute()
134        }
135    }
136
137    /// This function should be called instead of `results` on queries which
138    /// have no return value. It should never be called on a statement on
139    /// which `results` has previously been called?
140    pub(super) unsafe fn execute(self) -> QueryResult<StatementUse<'a>> {
141        unsafe {
142            ffi::mysql_stmt_execute(self.stmt.as_ptr());
143        }
144        self.did_an_error_occur()?;
145        unsafe {
146            ffi::mysql_stmt_store_result(self.stmt.as_ptr());
147        }
148        let ret = StatementUse { inner: self };
149        ret.inner.did_an_error_occur()?;
150        Ok(ret)
151    }
152}
153
154impl Drop for Statement {
155    fn drop(&mut self) {
156        unsafe { ffi::mysql_stmt_close(self.stmt.as_ptr()) };
157    }
158}
159
160#[allow(missing_debug_implementations)]
161pub(super) struct StatementUse<'a> {
162    inner: MaybeCached<'a, Statement>,
163}
164
165impl StatementUse<'_> {
166    pub(in crate::mysql::connection) fn affected_rows(&self) -> QueryResult<usize> {
167        let affected_rows = unsafe { ffi::mysql_stmt_affected_rows(self.inner.stmt.as_ptr()) };
168        affected_rows
169            .try_into()
170            .map_err(|e| Error::DeserializationError(Box::new(e)))
171    }
172
173    /// This function should be called after `execute` only
174    /// otherwise it's not guaranteed to return a valid result
175    pub(in crate::mysql::connection) unsafe fn result_size(&mut self) -> QueryResult<usize> {
176        let size = unsafe { ffi::mysql_stmt_num_rows(self.inner.stmt.as_ptr()) };
177        usize::try_from(size).map_err(|e| Error::DeserializationError(Box::new(e)))
178    }
179
180    pub(super) fn populate_row_buffers(&self, binds: &mut OutputBinds) -> QueryResult<Option<()>> {
181        let next_row_result = unsafe { ffi::mysql_stmt_fetch(self.inner.stmt.as_ptr()) };
182        if next_row_result < 0 {
183            self.inner.did_an_error_occur().map(Some)
184        } else {
185            #[allow(clippy::cast_sign_loss)] // that's how it's supposed to be based on the API
186            match next_row_result as libc::c_uint {
187                ffi::MYSQL_NO_DATA => Ok(None),
188                ffi::MYSQL_DATA_TRUNCATED => binds.populate_dynamic_buffers(self).map(Some),
189                0 => {
190                    binds.update_buffer_lengths();
191                    Ok(Some(()))
192                }
193                _error => self.inner.did_an_error_occur().map(Some),
194            }
195        }
196    }
197
198    pub(in crate::mysql::connection) unsafe fn fetch_column(
199        &self,
200        bind: &mut ffi::MYSQL_BIND,
201        idx: usize,
202        offset: usize,
203    ) -> QueryResult<()> {
204        unsafe {
205            ffi::mysql_stmt_fetch_column(
206                self.inner.stmt.as_ptr(),
207                bind,
208                idx.try_into()
209                    .map_err(|e| Error::DeserializationError(Box::new(e)))?,
210                offset as libc::c_ulong,
211            );
212        }
213        self.inner.did_an_error_occur()
214    }
215
216    /// If the pointers referenced by the `MYSQL_BIND` structures are invalidated,
217    /// you must call this function again before calling `mysql_stmt_fetch`.
218    pub(in crate::mysql::connection) unsafe fn bind_result(
219        &self,
220        binds: *mut ffi::MYSQL_BIND,
221    ) -> QueryResult<()> {
222        unsafe { self.inner.bind_result(binds) }
223    }
224}
225
226impl Drop for StatementUse<'_> {
227    fn drop(&mut self) {
228        unsafe {
229            ffi::mysql_stmt_free_result(self.inner.stmt.as_ptr());
230        }
231    }
232}