Skip to main content

diesel/mysql_like/connection/
raw.rs

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