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