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