Skip to main content

diesel/mysql_like/connection/
raw.rs

1#![allow(unsafe_code)] // module uses ffi
2use alloc::ffi::CString;
3use core::ffi as libc;
4use core::ffi::CStr;
5use core::ptr::{self, NonNull};
6use mysqlclient_sys as ffi;
7use std::sync::Once;
8
9use super::error_information::{MysqlLikeErrorInformation, sqlstate_from_ptr};
10use super::statement_cache::PrepareForCache;
11use super::stmt::Statement;
12use super::url::ConnectionOptions;
13use crate::mysql_like::{MysqlLikeBackend, MysqlType};
14use crate::result::{ConnectionError, ConnectionResult, QueryResult, SqlState};
15
16pub(super) struct RawConnection(NonNull<ffi::MYSQL>);
17
18// old versions of mysqlclient do not expose
19// ffi::FALSE, so we need to have our own compatibility
20// wrapper here
21//
22// Depending on the bindings version ffi::my_bool
23// might be an actual bool or a i8. For the former
24// case `default()` corresponds to `false` for the later
25// to `0` which is both interpreted as false
26#[inline(always)]
27pub(super) fn ffi_false() -> ffi::my_bool {
28    Default::default()
29}
30
31impl RawConnection {
32    pub(super) fn new() -> Self {
33        perform_thread_unsafe_library_initialization();
34        let raw_connection = unsafe { ffi::mysql_init(ptr::null_mut()) };
35        // We're trusting https://dev.mysql.com/doc/refman/5.7/en/mysql-init.html
36        // that null return always means OOM
37        let raw_connection =
38            NonNull::new(raw_connection).expect("Insufficient memory to allocate connection");
39        let result = RawConnection(raw_connection);
40
41        // This is only non-zero for unrecognized options, which should never happen.
42        let charset_result = unsafe {
43            ffi::mysql_options(
44                result.0.as_ptr(),
45                ffi::mysql_option::MYSQL_SET_CHARSET_NAME,
46                c"utf8mb4".as_ptr() as *const libc::c_void,
47            )
48        };
49        {
    match (&0, &charset_result) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val,
                    ::core::option::Option::Some(format_args!("MYSQL_SET_CHARSET_NAME was not recognized as an option by MySQL. This should never happen.")));
            }
        }
    }
};assert_eq!(
50            0, charset_result,
51            "MYSQL_SET_CHARSET_NAME was not \
52             recognized as an option by MySQL. This should never \
53             happen."
54        );
55
56        result
57    }
58
59    pub(super) fn connect(&self, connection_options: &ConnectionOptions) -> ConnectionResult<()> {
60        let host = connection_options.host();
61        let user = connection_options.user();
62        let password = connection_options.password();
63        let database = connection_options.database();
64        let port = connection_options.port();
65        let unix_socket = connection_options.unix_socket();
66        let client_flags = connection_options.client_flags();
67        let local_infile = connection_options.local_infile();
68
69        if let Some(ssl_mode) = connection_options.ssl_mode() {
70            self.set_ssl_mode(ssl_mode)
71        }
72        if let Some(ssl_ca) = connection_options.ssl_ca() {
73            self.set_ssl_ca(ssl_ca)
74        }
75        if let Some(ssl_cert) = connection_options.ssl_cert() {
76            self.set_ssl_cert(ssl_cert)
77        }
78        if let Some(ssl_key) = connection_options.ssl_key() {
79            self.set_ssl_key(ssl_key)
80        }
81        if let Some(local_infile) = local_infile {
82            self.set_local_infile(local_infile)
83        }
84
85        unsafe {
86            // Make sure you don't use the fake one!
87            ffi::mysql_real_connect(
88                self.0.as_ptr(),
89                host.map(CStr::as_ptr).unwrap_or_else(ptr::null),
90                user.as_ptr(),
91                password.map(CStr::as_ptr).unwrap_or_else(ptr::null),
92                database.map(CStr::as_ptr).unwrap_or_else(ptr::null),
93                u32::from(port.unwrap_or(0)),
94                unix_socket.map(CStr::as_ptr).unwrap_or_else(ptr::null),
95                client_flags.bits().into(),
96            )
97        };
98
99        let last_error_message = self.last_error_message();
100        if last_error_message.is_empty() {
101            let v = true as u32;
102            let v_ptr: *const u32 = &v;
103            // make sure to explicitly require the client to report data truncation
104            unsafe {
105                ffi::mysql_options(
106                    self.0.as_ptr(),
107                    ffi::mysql_option::MYSQL_REPORT_DATA_TRUNCATION,
108                    v_ptr as *const libc::c_void,
109                );
110            }
111            Ok(())
112        } else {
113            Err(ConnectionError::BadConnection(last_error_message))
114        }
115    }
116
117    pub(super) fn last_error_message(&self) -> String {
118        unsafe { CStr::from_ptr(ffi::mysql_error(self.0.as_ptr())) }
119            .to_string_lossy()
120            .into_owned()
121    }
122
123    fn last_error_sqlstate(&self) -> Option<SqlState> {
124        // SAFETY: `self.0` is the live handle owned by `self`.
125        let sqlstate = unsafe { ffi::mysql_sqlstate(self.0.as_ptr()) };
126        // SAFETY: the result is null or the NUL terminated SQLSTATE stored on the
127        // handle, which no call changes while `self` is borrowed here.
128        unsafe { sqlstate_from_ptr(sqlstate) }
129    }
130
131    pub(super) fn server_info(&self) -> Option<&CStr> {
132        // SAFETY: `self.0` is the live handle owned by `self`, and the call only reads it.
133        let info = unsafe { ffi::mysql_get_server_info(self.0.as_ptr()) };
134        // SAFETY: a non-null result is the NUL-terminated version string stored on the
135        // handle, which only a reconnect, never enabled by diesel, or `mysql_close` in
136        // `Drop` replaces, so it stays valid and unchanged while `self` is borrowed.
137        (!info.is_null()).then(|| unsafe { CStr::from_ptr(info) })
138    }
139
140    pub(super) fn execute(&self, query: &str) -> QueryResult<()> {
141        use crate::result::Error::InvalidCString;
142
143        let query = CString::new(query).map_err(InvalidCString)?;
144        unsafe {
145            // Make sure you don't use the fake one!
146            ffi::mysql_real_query(
147                self.0.as_ptr(),
148                query.as_ptr() as *const libc::c_char,
149                query.count_bytes() as libc::c_ulong,
150            );
151        }
152        self.did_an_error_occur()?;
153        self.flush_pending_results()?;
154        Ok(())
155    }
156
157    pub(super) fn enable_multi_statements<T, F>(&self, f: F) -> QueryResult<T>
158    where
159        F: FnOnce() -> QueryResult<T>,
160    {
161        unsafe {
162            ffi::mysql_set_server_option(
163                self.0.as_ptr(),
164                ffi::enum_mysql_set_option::MYSQL_OPTION_MULTI_STATEMENTS_ON,
165            );
166        }
167        self.did_an_error_occur()?;
168
169        let result = f();
170
171        unsafe {
172            ffi::mysql_set_server_option(
173                self.0.as_ptr(),
174                ffi::enum_mysql_set_option::MYSQL_OPTION_MULTI_STATEMENTS_OFF,
175            );
176        }
177        self.did_an_error_occur()?;
178
179        result
180    }
181
182    pub(super) fn prepare<DB: MysqlLikeBackend>(
183        &self,
184        query: &str,
185        _: PrepareForCache,
186        _: &[MysqlType],
187    ) -> QueryResult<Statement<DB>> {
188        let stmt = unsafe { ffi::mysql_stmt_init(self.0.as_ptr()) };
189        // It is documented that the only reason `mysql_stmt_init` will fail
190        // is because of OOM.
191        // https://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-init.html
192        let stmt = NonNull::new(stmt).expect("Out of memory creating prepared statement");
193        let stmt = Statement::new(stmt);
194        stmt.prepare(query)?;
195        Ok(stmt)
196    }
197
198    fn did_an_error_occur(&self) -> QueryResult<()> {
199        use crate::result::DatabaseErrorKind;
200        use crate::result::Error::DatabaseError;
201
202        let error_message = self.last_error_message();
203        if error_message.is_empty() {
204            Ok(())
205        } else {
206            Err(DatabaseError(
207                DatabaseErrorKind::Unknown,
208                Box::new(MysqlLikeErrorInformation {
209                    message: error_message,
210                    sqlstate: self.last_error_sqlstate(),
211                }),
212            ))
213        }
214    }
215
216    fn flush_pending_results(&self) -> QueryResult<()> {
217        // We may have a result to process before advancing
218        self.consume_current_result()?;
219        while self.more_results() {
220            self.next_result()?;
221            self.consume_current_result()?;
222        }
223        Ok(())
224    }
225
226    fn consume_current_result(&self) -> QueryResult<()> {
227        unsafe {
228            let res = ffi::mysql_store_result(self.0.as_ptr());
229            if !res.is_null() {
230                ffi::mysql_free_result(res);
231            }
232        }
233        self.did_an_error_occur()
234    }
235
236    fn more_results(&self) -> bool {
237        unsafe { ffi::mysql_more_results(self.0.as_ptr()) != ffi_false() }
238    }
239
240    fn next_result(&self) -> QueryResult<()> {
241        unsafe { ffi::mysql_next_result(self.0.as_ptr()) };
242        self.did_an_error_occur()
243    }
244
245    fn set_ssl_mode(&self, ssl_mode: mysqlclient_sys::mysql_ssl_mode) {
246        let v = ssl_mode as u32;
247        let v_ptr: *const u32 = &v;
248        let n = ptr::NonNull::new(v_ptr as *mut u32).expect("NonNull::new failed");
249        unsafe {
250            mysqlclient_sys::mysql_options(
251                self.0.as_ptr(),
252                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_MODE,
253                n.as_ptr() as *const core::ffi::c_void,
254            )
255        };
256    }
257
258    fn set_ssl_ca(&self, ssl_ca: &CStr) {
259        unsafe {
260            mysqlclient_sys::mysql_options(
261                self.0.as_ptr(),
262                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_CA,
263                ssl_ca.as_ptr() as *const core::ffi::c_void,
264            )
265        };
266    }
267
268    fn set_ssl_cert(&self, ssl_cert: &CStr) {
269        unsafe {
270            mysqlclient_sys::mysql_options(
271                self.0.as_ptr(),
272                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_CERT,
273                ssl_cert.as_ptr() as *const core::ffi::c_void,
274            )
275        };
276    }
277
278    fn set_ssl_key(&self, ssl_key: &CStr) {
279        unsafe {
280            mysqlclient_sys::mysql_options(
281                self.0.as_ptr(),
282                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_KEY,
283                ssl_key.as_ptr() as *const core::ffi::c_void,
284            )
285        };
286    }
287
288    fn set_local_infile(&self, local_infile: bool) {
289        let v = local_infile as u32;
290        let v_ptr: *const u32 = &v;
291        let n = ptr::NonNull::new(v_ptr as *mut u32).expect("NonNull::new failed");
292        unsafe {
293            mysqlclient_sys::mysql_options(
294                self.0.as_ptr(),
295                mysqlclient_sys::mysql_option::MYSQL_OPT_LOCAL_INFILE,
296                n.as_ptr() as *const core::ffi::c_void,
297            )
298        };
299    }
300}
301
302impl Drop for RawConnection {
303    fn drop(&mut self) {
304        unsafe {
305            ffi::mysql_close(self.0.as_ptr());
306        }
307    }
308}
309
310/// > In a non-multi-threaded environment, `mysql_init()` invokes
311/// > `mysql_library_init()` automatically as necessary. However,
312/// > `mysql_library_init()` is not thread-safe in a multi-threaded environment,
313/// > and thus neither is `mysql_init()`. Before calling `mysql_init()`, either
314/// > call `mysql_library_init()` prior to spawning any threads, or use a mutex
315/// > to protect the `mysql_library_init()` call. This should be done prior to
316/// > any other client library call.
317///
318/// <https://dev.mysql.com/doc/c-api/8.4/en/mysql-init.html>
319static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new();
320
321fn perform_thread_unsafe_library_initialization() {
322    MYSQL_THREAD_UNSAFE_INIT.call_once(|| {
323        // mysql_library_init is defined by `#define mysql_library_init mysql_server_init`
324        // which isn't picked up by bindgen
325        let error_code = unsafe { ffi::mysql_server_init(0, ptr::null_mut(), ptr::null_mut()) };
326        if error_code != 0 {
327            // FIXME: This is documented as Nonzero if an error occurred.
328            // Presumably the value has some sort of meaning that we should
329            // reflect in this message. We are going to panic instead of return
330            // an error here, since the documentation does not indicate whether
331            // it is safe to call this function twice if the first call failed,
332            // so I will assume it is not.
333            {
    ::core::panicking::panic_fmt(format_args!("Unable to perform MySQL global initialization"));
};panic!("Unable to perform MySQL global initialization");
334        }
335    })
336}