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