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