1 extern crate mysqlclient_sys as ffi;
2 
3 use std::ffi::CStr;
4 use std::os::raw as libc;
5 use std::ptr::{self, NonNull};
6 use std::sync::Once;
7 
8 use super::stmt::Statement;
9 use super::url::ConnectionOptions;
10 use result::{ConnectionError, ConnectionResult, QueryResult};
11 
12 pub struct RawConnection(NonNull<ffi::MYSQL>);
13 
14 impl RawConnection {
new() -> Self15     pub fn new() -> Self {
16         perform_thread_unsafe_library_initialization();
17         let raw_connection = unsafe { ffi::mysql_init(ptr::null_mut()) };
18         // We're trusting https://dev.mysql.com/doc/refman/5.7/en/mysql-init.html
19         // that null return always means OOM
20         let raw_connection =
21             NonNull::new(raw_connection).expect("Insufficient memory to allocate connection");
22         let result = RawConnection(raw_connection);
23 
24         // This is only non-zero for unrecognized options, which should never happen.
25         let charset_result = unsafe {
26             ffi::mysql_options(
27                 result.0.as_ptr(),
28                 ffi::mysql_option::MYSQL_SET_CHARSET_NAME,
29                 b"utf8mb4\0".as_ptr() as *const libc::c_void,
30             )
31         };
32         assert_eq!(
33             0, charset_result,
34             "MYSQL_SET_CHARSET_NAME was not \
35              recognized as an option by MySQL. This should never \
36              happen."
37         );
38 
39         result
40     }
41 
connect(&self, connection_options: &ConnectionOptions) -> ConnectionResult<()>42     pub fn connect(&self, connection_options: &ConnectionOptions) -> ConnectionResult<()> {
43         let host = connection_options.host();
44         let user = connection_options.user();
45         let password = connection_options.password();
46         let database = connection_options.database();
47         let port = connection_options.port();
48 
49         unsafe {
50             // Make sure you don't use the fake one!
51             ffi::mysql_real_connect(
52                 self.0.as_ptr(),
53                 host.map(CStr::as_ptr).unwrap_or_else(|| ptr::null_mut()),
54                 user.as_ptr(),
55                 password
56                     .map(CStr::as_ptr)
57                     .unwrap_or_else(|| ptr::null_mut()),
58                 database
59                     .map(CStr::as_ptr)
60                     .unwrap_or_else(|| ptr::null_mut()),
61                 u32::from(port.unwrap_or(0)),
62                 ptr::null_mut(),
63                 0,
64             )
65         };
66 
67         let last_error_message = self.last_error_message();
68         if last_error_message.is_empty() {
69             Ok(())
70         } else {
71             Err(ConnectionError::BadConnection(last_error_message))
72         }
73     }
74 
last_error_message(&self) -> String75     pub fn last_error_message(&self) -> String {
76         unsafe { CStr::from_ptr(ffi::mysql_error(self.0.as_ptr())) }
77             .to_string_lossy()
78             .into_owned()
79     }
80 
execute(&self, query: &str) -> QueryResult<()>81     pub fn execute(&self, query: &str) -> QueryResult<()> {
82         unsafe {
83             // Make sure you don't use the fake one!
84             ffi::mysql_real_query(
85                 self.0.as_ptr(),
86                 query.as_ptr() as *const libc::c_char,
87                 query.len() as libc::c_ulong,
88             );
89         }
90         self.did_an_error_occur()?;
91         self.flush_pending_results()?;
92         Ok(())
93     }
94 
enable_multi_statements<T, F>(&self, f: F) -> QueryResult<T> where F: FnOnce() -> QueryResult<T>,95     pub fn enable_multi_statements<T, F>(&self, f: F) -> QueryResult<T>
96     where
97         F: FnOnce() -> QueryResult<T>,
98     {
99         unsafe {
100             ffi::mysql_set_server_option(
101                 self.0.as_ptr(),
102                 ffi::enum_mysql_set_option::MYSQL_OPTION_MULTI_STATEMENTS_ON,
103             );
104         }
105         self.did_an_error_occur()?;
106 
107         let result = f();
108 
109         unsafe {
110             ffi::mysql_set_server_option(
111                 self.0.as_ptr(),
112                 ffi::enum_mysql_set_option::MYSQL_OPTION_MULTI_STATEMENTS_OFF,
113             );
114         }
115         self.did_an_error_occur()?;
116 
117         result
118     }
119 
affected_rows(&self) -> usize120     pub fn affected_rows(&self) -> usize {
121         let affected_rows = unsafe { ffi::mysql_affected_rows(self.0.as_ptr()) };
122         affected_rows as usize
123     }
124 
prepare(&self, query: &str) -> QueryResult<Statement>125     pub fn prepare(&self, query: &str) -> QueryResult<Statement> {
126         let stmt = unsafe { ffi::mysql_stmt_init(self.0.as_ptr()) };
127         // It is documented that the only reason `mysql_stmt_init` will fail
128         // is because of OOM.
129         // https://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-init.html
130         let stmt = NonNull::new(stmt).expect("Out of memory creating prepared statement");
131         let stmt = Statement::new(stmt);
132         stmt.prepare(query)?;
133         Ok(stmt)
134     }
135 
did_an_error_occur(&self) -> QueryResult<()>136     fn did_an_error_occur(&self) -> QueryResult<()> {
137         use result::DatabaseErrorKind;
138         use result::Error::DatabaseError;
139 
140         let error_message = self.last_error_message();
141         if error_message.is_empty() {
142             Ok(())
143         } else {
144             Err(DatabaseError(
145                 DatabaseErrorKind::__Unknown,
146                 Box::new(error_message),
147             ))
148         }
149     }
150 
flush_pending_results(&self) -> QueryResult<()>151     fn flush_pending_results(&self) -> QueryResult<()> {
152         // We may have a result to process before advancing
153         self.consume_current_result()?;
154         while self.more_results() {
155             self.next_result()?;
156             self.consume_current_result()?;
157         }
158         Ok(())
159     }
160 
consume_current_result(&self) -> QueryResult<()>161     fn consume_current_result(&self) -> QueryResult<()> {
162         unsafe {
163             let res = ffi::mysql_store_result(self.0.as_ptr());
164             if !res.is_null() {
165                 ffi::mysql_free_result(res);
166             }
167         }
168         self.did_an_error_occur()
169     }
170 
more_results(&self) -> bool171     fn more_results(&self) -> bool {
172         unsafe { ffi::mysql_more_results(self.0.as_ptr()) != 0 }
173     }
174 
next_result(&self) -> QueryResult<()>175     fn next_result(&self) -> QueryResult<()> {
176         unsafe { ffi::mysql_next_result(self.0.as_ptr()) };
177         self.did_an_error_occur()
178     }
179 }
180 
181 impl Drop for RawConnection {
drop(&mut self)182     fn drop(&mut self) {
183         unsafe {
184             ffi::mysql_close(self.0.as_ptr());
185         }
186     }
187 }
188 
189 /// > In a non-multi-threaded environment, `mysql_init()` invokes
190 /// > `mysql_library_init()` automatically as necessary. However,
191 /// > `mysql_library_init()` is not thread-safe in a multi-threaded environment,
192 /// > and thus neither is `mysql_init()`. Before calling `mysql_init()`, either
193 /// > call `mysql_library_init()` prior to spawning any threads, or use a mutex
194 /// > to protect the `mysql_library_init()` call. This should be done prior to
195 /// > any other client library call.
196 ///
197 /// <https://dev.mysql.com/doc/refman/5.7/en/mysql-init.html>
198 static MYSQL_THREAD_UNSAFE_INIT: Once = Once::new();
199 
perform_thread_unsafe_library_initialization()200 fn perform_thread_unsafe_library_initialization() {
201     MYSQL_THREAD_UNSAFE_INIT.call_once(|| {
202         // mysql_library_init is defined by `#define mysql_library_init mysql_server_init`
203         // which isn't picked up by bindgen
204         let error_code = unsafe { ffi::mysql_server_init(0, ptr::null_mut(), ptr::null_mut()) };
205         if error_code != 0 {
206             // FIXME: This is documented as Nonzero if an error occurred.
207             // Presumably the value has some sort of meaning that we should
208             // reflect in this message. We are going to panic instead of return
209             // an error here, since the documentation does not indicate whether
210             // it is safe to call this function twice if the first call failed,
211             // so I will assume it is not.
212             panic!("Unable to perform MySQL global initialization");
213         }
214     })
215 }
216