Skip to main content

diesel/mysql/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::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        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!(
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        let local_infile = connection_options.local_infile();
66
67        if let Some(ssl_mode) = connection_options.ssl_mode() {
68            self.set_ssl_mode(ssl_mode)
69        }
70        if let Some(ssl_ca) = connection_options.ssl_ca() {
71            self.set_ssl_ca(ssl_ca)
72        }
73        if let Some(ssl_cert) = connection_options.ssl_cert() {
74            self.set_ssl_cert(ssl_cert)
75        }
76        if let Some(ssl_key) = connection_options.ssl_key() {
77            self.set_ssl_key(ssl_key)
78        }
79        if let Some(local_infile) = local_infile {
80            self.set_local_infile(local_infile)
81        }
82
83        unsafe {
84            // Make sure you don't use the fake one!
85            ffi::mysql_real_connect(
86                self.0.as_ptr(),
87                host.map(CStr::as_ptr).unwrap_or_else(ptr::null),
88                user.as_ptr(),
89                password.map(CStr::as_ptr).unwrap_or_else(ptr::null),
90                database.map(CStr::as_ptr).unwrap_or_else(ptr::null),
91                u32::from(port.unwrap_or(0)),
92                unix_socket.map(CStr::as_ptr).unwrap_or_else(ptr::null),
93                client_flags.bits().into(),
94            )
95        };
96
97        let last_error_message = self.last_error_message();
98        if last_error_message.is_empty() {
99            Ok(())
100        } else {
101            Err(ConnectionError::BadConnection(last_error_message))
102        }
103    }
104
105    pub(super) fn last_error_message(&self) -> String {
106        unsafe { CStr::from_ptr(ffi::mysql_error(self.0.as_ptr())) }
107            .to_string_lossy()
108            .into_owned()
109    }
110
111    pub(super) fn execute(&self, query: &str) -> QueryResult<()> {
112        unsafe {
113            // Make sure you don't use the fake one!
114            ffi::mysql_real_query(
115                self.0.as_ptr(),
116                query.as_ptr() as *const libc::c_char,
117                query.len() as libc::c_ulong,
118            );
119        }
120        self.did_an_error_occur()?;
121        self.flush_pending_results()?;
122        Ok(())
123    }
124
125    pub(super) fn enable_multi_statements<T, F>(&self, f: F) -> QueryResult<T>
126    where
127        F: FnOnce() -> QueryResult<T>,
128    {
129        unsafe {
130            ffi::mysql_set_server_option(
131                self.0.as_ptr(),
132                ffi::enum_mysql_set_option::MYSQL_OPTION_MULTI_STATEMENTS_ON,
133            );
134        }
135        self.did_an_error_occur()?;
136
137        let result = f();
138
139        unsafe {
140            ffi::mysql_set_server_option(
141                self.0.as_ptr(),
142                ffi::enum_mysql_set_option::MYSQL_OPTION_MULTI_STATEMENTS_OFF,
143            );
144        }
145        self.did_an_error_occur()?;
146
147        result
148    }
149
150    pub(super) fn prepare(
151        &self,
152        query: &str,
153        _: PrepareForCache,
154        _: &[MysqlType],
155    ) -> QueryResult<Statement> {
156        let stmt = unsafe { ffi::mysql_stmt_init(self.0.as_ptr()) };
157        // It is documented that the only reason `mysql_stmt_init` will fail
158        // is because of OOM.
159        // https://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-init.html
160        let stmt = NonNull::new(stmt).expect("Out of memory creating prepared statement");
161        let stmt = Statement::new(stmt);
162        stmt.prepare(query)?;
163        Ok(stmt)
164    }
165
166    fn did_an_error_occur(&self) -> QueryResult<()> {
167        use crate::result::DatabaseErrorKind;
168        use crate::result::Error::DatabaseError;
169
170        let error_message = self.last_error_message();
171        if error_message.is_empty() {
172            Ok(())
173        } else {
174            Err(DatabaseError(
175                DatabaseErrorKind::Unknown,
176                Box::new(error_message),
177            ))
178        }
179    }
180
181    fn flush_pending_results(&self) -> QueryResult<()> {
182        // We may have a result to process before advancing
183        self.consume_current_result()?;
184        while self.more_results() {
185            self.next_result()?;
186            self.consume_current_result()?;
187        }
188        Ok(())
189    }
190
191    fn consume_current_result(&self) -> QueryResult<()> {
192        unsafe {
193            let res = ffi::mysql_store_result(self.0.as_ptr());
194            if !res.is_null() {
195                ffi::mysql_free_result(res);
196            }
197        }
198        self.did_an_error_occur()
199    }
200
201    fn more_results(&self) -> bool {
202        unsafe { ffi::mysql_more_results(self.0.as_ptr()) != ffi_false() }
203    }
204
205    fn next_result(&self) -> QueryResult<()> {
206        unsafe { ffi::mysql_next_result(self.0.as_ptr()) };
207        self.did_an_error_occur()
208    }
209
210    fn set_ssl_mode(&self, ssl_mode: mysqlclient_sys::mysql_ssl_mode) {
211        let v = ssl_mode as u32;
212        let v_ptr: *const u32 = &v;
213        let n = ptr::NonNull::new(v_ptr as *mut u32).expect("NonNull::new failed");
214        unsafe {
215            mysqlclient_sys::mysql_options(
216                self.0.as_ptr(),
217                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_MODE,
218                n.as_ptr() as *const core::ffi::c_void,
219            )
220        };
221    }
222
223    fn set_ssl_ca(&self, ssl_ca: &CStr) {
224        unsafe {
225            mysqlclient_sys::mysql_options(
226                self.0.as_ptr(),
227                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_CA,
228                ssl_ca.as_ptr() as *const core::ffi::c_void,
229            )
230        };
231    }
232
233    fn set_ssl_cert(&self, ssl_cert: &CStr) {
234        unsafe {
235            mysqlclient_sys::mysql_options(
236                self.0.as_ptr(),
237                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_CERT,
238                ssl_cert.as_ptr() as *const core::ffi::c_void,
239            )
240        };
241    }
242
243    fn set_ssl_key(&self, ssl_key: &CStr) {
244        unsafe {
245            mysqlclient_sys::mysql_options(
246                self.0.as_ptr(),
247                mysqlclient_sys::mysql_option::MYSQL_OPT_SSL_KEY,
248                ssl_key.as_ptr() as *const core::ffi::c_void,
249            )
250        };
251    }
252
253    fn set_local_infile(&self, local_infile: bool) {
254        let v = local_infile as u32;
255        let v_ptr: *const u32 = &v;
256        let n = ptr::NonNull::new(v_ptr as *mut u32).expect("NonNull::new failed");
257        unsafe {
258            mysqlclient_sys::mysql_options(
259                self.0.as_ptr(),
260                mysqlclient_sys::mysql_option::MYSQL_OPT_LOCAL_INFILE,
261                n.as_ptr() as *const core::ffi::c_void,
262            )
263        };
264    }
265}
266
267impl Drop for RawConnection {
268    fn drop(&mut self) {
269        unsafe {
270            ffi::mysql_close(self.0.as_ptr());
271        }
272    }
273}
274
275/// > In a non-multi-threaded environment, `mysql_init()` invokes
276/// > `mysql_library_init()` automatically as necessary. However,
277/// > `mysql_library_init()` is not thread-safe in a multi-threaded environment,
278/// > and thus neither is `mysql_init()`. Before calling `mysql_init()`, either
279/// > call `mysql_library_init()` prior to spawning any threads, or use a mutex
280/// > to protect the `mysql_library_init()` call. This should be done prior to
281/// > any other client library call.
282///
283/// <https://dev.mysql.com/doc/c-api/8.4/en/mysql-init.html>
284static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new();
285
286fn perform_thread_unsafe_library_initialization() {
287    MYSQL_THREAD_UNSAFE_INIT.call_once(|| {
288        // mysql_library_init is defined by `#define mysql_library_init mysql_server_init`
289        // which isn't picked up by bindgen
290        let error_code = unsafe { ffi::mysql_server_init(0, ptr::null_mut(), ptr::null_mut()) };
291        if error_code != 0 {
292            // FIXME: This is documented as Nonzero if an error occurred.
293            // Presumably the value has some sort of meaning that we should
294            // reflect in this message. We are going to panic instead of return
295            // an error here, since the documentation does not indicate whether
296            // it is safe to call this function twice if the first call failed,
297            // so I will assume it is not.
298            {
    ::core::panicking::panic_fmt(format_args!("Unable to perform MySQL global initialization"));
};panic!("Unable to perform MySQL global initialization");
299        }
300    })
301}