odbc_api/handles/connection.rs
1use super::{
2 OutputStringBuffer, SqlResult,
3 as_handle::AsHandle,
4 buffer::mut_buf_ptr,
5 drop_handle,
6 sql_char::{
7 SqlChar, SqlText, binary_length, is_truncated_bin, resize_to_fit_with_tz,
8 resize_to_fit_without_tz,
9 },
10 sql_result::ExtSqlReturn,
11 statement::StatementImpl,
12};
13use log::debug;
14use odbc_sys::{
15 CompletionType, ConnectionAttribute, DriverConnectOption, HDbc, HEnv, HStmt, HWnd, Handle,
16 HandleType, IS_UINTEGER, InfoType, Pointer, SQLAllocHandle, SQLDisconnect, SQLEndTran,
17};
18use std::{ffi::c_void, marker::PhantomData, mem::size_of, ptr::null_mut};
19
20#[cfg(not(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows"))))]
21use odbc_sys::{
22 SQLConnect as sql_connect, SQLDriverConnect as sql_driver_connect,
23 SQLGetConnectAttr as sql_get_connect_attr, SQLGetInfo as sql_get_info,
24 SQLSetConnectAttr as sql_set_connect_attr,
25};
26
27#[cfg(any(feature = "wide", all(not(feature = "narrow"), target_os = "windows")))]
28use odbc_sys::{
29 SQLConnectW as sql_connect, SQLDriverConnectW as sql_driver_connect,
30 SQLGetConnectAttrW as sql_get_connect_attr, SQLGetInfoW as sql_get_info,
31 SQLSetConnectAttrW as sql_set_connect_attr,
32};
33
34/// The connection handle references storage of all information about the connection to the data
35/// source, including status, transaction state, and error information.
36///
37/// Connection is not `Sync`, this implies that many methods which one would suspect should take
38/// `&mut self` are actually `&self`. This is important if several statement exists which borrow
39/// the same connection.
40pub struct Connection<'c> {
41 parent: PhantomData<&'c HEnv>,
42 handle: HDbc,
43}
44
45unsafe impl AsHandle for Connection<'_> {
46 fn as_handle(&self) -> Handle {
47 self.handle as Handle
48 }
49
50 fn handle_type(&self) -> HandleType {
51 HandleType::Dbc
52 }
53}
54
55impl Drop for Connection<'_> {
56 fn drop(&mut self) {
57 unsafe {
58 drop_handle(self.handle as Handle, HandleType::Dbc);
59 }
60 }
61}
62
63/// According to the ODBC documentation this is safe. See:
64/// <https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/multithreading>
65///
66/// In addition to that, this has not caused trouble in a while. So we mark sending connections to
67/// other threads as safe. Reading through the documentation, one might get the impression that
68/// Connections are also `Sync`. This could be theoretically true on the level of the handle, but at
69/// the latest once the interior mutability due to error handling comes in to play, higher level
70/// abstraction have to content themselves with `Send`. This is currently how far my trust with most
71/// ODBC drivers.
72unsafe impl Send for Connection<'_> {}
73
74impl Connection<'_> {
75 /// # Safety
76 ///
77 /// Call this method only with a valid (successfully allocated) ODBC connection handle.
78 pub unsafe fn new(handle: HDbc) -> Self {
79 Self {
80 handle,
81 parent: PhantomData,
82 }
83 }
84
85 /// Directly acces the underlying ODBC handle.
86 pub fn as_sys(&self) -> HDbc {
87 self.handle
88 }
89
90 /// Establishes connections to a driver and a data source.
91 ///
92 /// * See [Connecting with SQLConnect][1]
93 /// * See [SQLConnectFunction][2]
94 ///
95 /// # Arguments
96 ///
97 /// * `data_source_name` - Data source name. The data might be located on the same computer as
98 /// the program, or on another computer somewhere on a network.
99 /// * `user` - User identifier.
100 /// * `pwd` - Authentication string (typically the password).
101 ///
102 /// [1]: https://docs.microsoft.com//sql/odbc/reference/develop-app/connecting-with-sqlconnect
103 /// [2]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlconnect-function
104 pub fn connect(
105 &mut self,
106 data_source_name: &SqlText,
107 user: &SqlText,
108 pwd: &SqlText,
109 ) -> SqlResult<()> {
110 unsafe {
111 sql_connect(
112 self.handle,
113 data_source_name.ptr(),
114 data_source_name.len_char().try_into().unwrap(),
115 user.ptr(),
116 user.len_char().try_into().unwrap(),
117 pwd.ptr(),
118 pwd.len_char().try_into().unwrap(),
119 )
120 .into_sql_result("SQLConnect")
121 }
122 }
123
124 /// An alternative to `connect`. It supports data sources that require more connection
125 /// information than the three arguments in `connect` and data sources that are not defined in
126 /// the system information.
127 pub fn connect_with_connection_string(&mut self, connection_string: &SqlText) -> SqlResult<()> {
128 unsafe {
129 let parent_window = null_mut();
130 let mut completed_connection_string = OutputStringBuffer::empty();
131
132 self.driver_connect(
133 connection_string,
134 parent_window,
135 &mut completed_connection_string,
136 DriverConnectOption::NoPrompt,
137 )
138 // Since we did pass NoPrompt we know the user can not abort the prompt.
139 .map(|_connection_string_is_complete| ())
140 }
141 }
142
143 /// An alternative to `connect` for connecting with a connection string. Allows for completing
144 /// a connection string with a GUI prompt on windows.
145 ///
146 /// # Return
147 ///
148 /// [`SqlResult::NoData`] in case the prompt completing the connection string has been aborted.
149 ///
150 /// # Safety
151 ///
152 /// `parent_window` must either be a valid window handle or `NULL`.
153 pub unsafe fn driver_connect(
154 &mut self,
155 connection_string: &SqlText,
156 parent_window: HWnd,
157 completed_connection_string: &mut OutputStringBuffer,
158 driver_completion: DriverConnectOption,
159 ) -> SqlResult<()> {
160 unsafe {
161 sql_driver_connect(
162 self.handle,
163 parent_window,
164 connection_string.ptr(),
165 connection_string.len_char().try_into().unwrap(),
166 completed_connection_string.mut_buf_ptr(),
167 completed_connection_string.buf_len(),
168 completed_connection_string.mut_actual_len_ptr(),
169 driver_completion,
170 )
171 .into_sql_result("SQLDriverConnect")
172 }
173 }
174
175 /// Disconnect from an ODBC data source.
176 pub fn disconnect(&mut self) -> SqlResult<()> {
177 unsafe { SQLDisconnect(self.handle).into_sql_result("SQLDisconnect") }
178 }
179
180 /// Allocate a new statement handle. The `Statement` must not outlive the `Connection`.
181 pub fn allocate_statement(&self) -> SqlResult<StatementImpl<'_>> {
182 let mut out = null_mut();
183 unsafe {
184 SQLAllocHandle(HandleType::Stmt, self.as_handle(), &mut out)
185 .into_sql_result("SQLAllocHandle")
186 .on_success(|| StatementImpl::new(out as HStmt))
187 }
188 }
189
190 /// Specify the transaction mode. By default, ODBC transactions are in auto-commit mode (unless
191 /// SQLSetConnectAttr and SQLSetConnectOption are not supported, which is unlikely). Switching
192 /// from manual-commit mode to auto-commit mode automatically commits any open transaction on
193 /// the connection.
194 pub fn set_autocommit(&self, enabled: bool) -> SqlResult<()> {
195 let val = enabled as u32;
196 unsafe {
197 sql_set_connect_attr(
198 self.handle,
199 ConnectionAttribute::AutoCommit,
200 val as Pointer,
201 0, // will be ignored according to ODBC spec
202 )
203 .into_sql_result("SQLSetConnectAttr")
204 }
205 }
206
207 /// Number of seconds to wait for a login request to complete before returning to the
208 /// application. The default is driver-dependent. If `0` the timeout is dasabled and a
209 /// connection attempt will wait indefinitely.
210 ///
211 /// If the specified timeout exceeds the maximum login timeout in the data source, the driver
212 /// substitutes that value and uses the maximum login timeout instead.
213 ///
214 /// This corresponds to the `SQL_ATTR_LOGIN_TIMEOUT` attribute in the ODBC specification.
215 ///
216 /// See:
217 /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
218 pub fn set_login_timeout_sec(&self, timeout: u32) -> SqlResult<()> {
219 unsafe {
220 sql_set_connect_attr(
221 self.handle,
222 ConnectionAttribute::LoginTimeout,
223 timeout as Pointer,
224 0,
225 )
226 .into_sql_result("SQLSetConnectAttr")
227 }
228 }
229
230 /// Specifying the network packet size in bytes. Note: Many data sources either do not support
231 /// this option or only can return but not set the network packet size. If the specified size
232 /// exceeds the maximum packet size or is smaller than the minimum packet size, the driver
233 /// substitutes that value and returns SQLSTATE 01S02 (Option value changed). If the application
234 /// sets packet size after a connection has already been made, the driver will return SQLSTATE
235 /// HY011 (Attribute cannot be set now).
236 ///
237 /// See:
238 /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
239 pub fn set_packet_size(&self, packet_size: u32) -> SqlResult<()> {
240 unsafe {
241 sql_set_connect_attr(
242 self.handle,
243 ConnectionAttribute::PacketSize,
244 packet_size as Pointer,
245 0,
246 )
247 .into_sql_result("SQLSetConnectAttr")
248 }
249 }
250
251 /// To commit a transaction in manual-commit mode.
252 pub fn commit(&self) -> SqlResult<()> {
253 unsafe {
254 SQLEndTran(HandleType::Dbc, self.as_handle(), CompletionType::Commit)
255 .into_sql_result("SQLEndTran")
256 }
257 }
258
259 /// Roll back a transaction in manual-commit mode.
260 pub fn rollback(&self) -> SqlResult<()> {
261 unsafe {
262 SQLEndTran(HandleType::Dbc, self.as_handle(), CompletionType::Rollback)
263 .into_sql_result("SQLEndTran")
264 }
265 }
266
267 /// Fetch the name of the database management system used by the connection and store it into
268 /// the provided `buf`.
269 pub fn fetch_database_management_system_name(&self, buf: &mut Vec<SqlChar>) -> SqlResult<()> {
270 // String length in bytes, not characters. Terminating zero is excluded.
271 let mut string_length_in_bytes: i16 = 0;
272 // Let's utilize all of `buf`s capacity.
273 buf.resize(buf.capacity(), 0);
274
275 unsafe {
276 let mut res = sql_get_info(
277 self.handle,
278 InfoType::DbmsName,
279 mut_buf_ptr(buf) as Pointer,
280 binary_length(buf).try_into().unwrap(),
281 &mut string_length_in_bytes as *mut i16,
282 )
283 .into_sql_result("SQLGetInfo");
284
285 if res.is_err() {
286 return res;
287 }
288
289 // Call has been a success but let's check if the buffer had been large enough.
290 if is_truncated_bin(buf, string_length_in_bytes.try_into().unwrap()) {
291 // It seems we must try again with a large enough buffer.
292 resize_to_fit_with_tz(buf, string_length_in_bytes.try_into().unwrap());
293 res = sql_get_info(
294 self.handle,
295 InfoType::DbmsName,
296 mut_buf_ptr(buf) as Pointer,
297 binary_length(buf).try_into().unwrap(),
298 &mut string_length_in_bytes as *mut i16,
299 )
300 .into_sql_result("SQLGetInfo");
301
302 if res.is_err() {
303 return res;
304 }
305 }
306
307 // Resize buffer to exact string length without terminal zero
308 resize_to_fit_without_tz(buf, string_length_in_bytes.try_into().unwrap());
309 res
310 }
311 }
312
313 fn info_u16(&self, info_type: InfoType) -> SqlResult<u16> {
314 unsafe {
315 let mut value = 0u16;
316 sql_get_info(
317 self.handle,
318 info_type,
319 &mut value as *mut u16 as Pointer,
320 // Buffer length should not be required in this case, according to the ODBC
321 // documentation at https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetinfo-function?view=sql-server-ver15#arguments
322 // However, in practice some drivers (such as Microsoft Access) require it to be
323 // specified explicitly here, otherwise they return an error without diagnostics.
324 size_of::<*mut u16>() as i16,
325 null_mut(),
326 )
327 .into_sql_result("SQLGetInfo")
328 .on_success(|| value)
329 }
330 }
331
332 /// Maximum length of catalog names.
333 pub fn max_catalog_name_len(&self) -> SqlResult<u16> {
334 self.info_u16(InfoType::MaxCatalogNameLen)
335 }
336
337 /// Maximum length of schema names.
338 pub fn max_schema_name_len(&self) -> SqlResult<u16> {
339 self.info_u16(InfoType::MaxSchemaNameLen)
340 }
341
342 /// Maximum length of table names.
343 pub fn max_table_name_len(&self) -> SqlResult<u16> {
344 self.info_u16(InfoType::MaxTableNameLen)
345 }
346
347 /// Maximum length of column names.
348 pub fn max_column_name_len(&self) -> SqlResult<u16> {
349 self.info_u16(InfoType::MaxColumnNameLen)
350 }
351
352 /// Fetch the name of the current catalog being used by the connection and store it into the
353 /// provided `buf`.
354 pub fn fetch_current_catalog(&self, buffer: &mut Vec<SqlChar>) -> SqlResult<()> {
355 // String length in bytes, not characters. Terminating zero is excluded.
356 let mut string_length_in_bytes: i32 = 0;
357 // Let's utilize all of `buf`s capacity.
358 buffer.resize(buffer.capacity(), 0);
359
360 unsafe {
361 let mut res = sql_get_connect_attr(
362 self.handle,
363 ConnectionAttribute::CurrentCatalog,
364 mut_buf_ptr(buffer) as Pointer,
365 binary_length(buffer).try_into().unwrap(),
366 &mut string_length_in_bytes as *mut i32,
367 )
368 .into_sql_result("SQLGetConnectAttr");
369
370 if res.is_err() {
371 return res;
372 }
373
374 if is_truncated_bin(buffer, string_length_in_bytes.try_into().unwrap()) {
375 resize_to_fit_with_tz(buffer, string_length_in_bytes.try_into().unwrap());
376 res = sql_get_connect_attr(
377 self.handle,
378 ConnectionAttribute::CurrentCatalog,
379 mut_buf_ptr(buffer) as Pointer,
380 binary_length(buffer).try_into().unwrap(),
381 &mut string_length_in_bytes as *mut i32,
382 )
383 .into_sql_result("SQLGetConnectAttr");
384 }
385
386 if res.is_err() {
387 return res;
388 }
389
390 // Resize buffer to exact string length without terminal zero
391 resize_to_fit_without_tz(buffer, string_length_in_bytes.try_into().unwrap());
392 res
393 }
394 }
395
396 /// Indicates the state of the connection. If `true` the connection has been lost. If `false`,
397 /// the connection is still active.
398 pub fn is_dead(&self) -> SqlResult<bool> {
399 unsafe {
400 self.attribute_u32(ConnectionAttribute::ConnectionDead)
401 .map(|v| match v {
402 0 => false,
403 1 => true,
404 other => panic!("Unexpected result value from SQLGetConnectAttr: {other}"),
405 })
406 }
407 }
408
409 /// Networ packet size in bytes.
410 pub fn packet_size(&self) -> SqlResult<u32> {
411 unsafe { self.attribute_u32(ConnectionAttribute::PacketSize) }
412 }
413
414 /// # Safety
415 ///
416 /// Caller must ensure connection attribute is numeric.
417 unsafe fn attribute_u32(&self, attribute: ConnectionAttribute) -> SqlResult<u32> {
418 let mut out: u32 = 0;
419 unsafe {
420 sql_get_connect_attr(
421 self.handle,
422 attribute,
423 &mut out as *mut u32 as *mut c_void,
424 IS_UINTEGER,
425 null_mut(),
426 )
427 }
428 .into_sql_result("SQLGetConnectAttr")
429 .on_success(|| {
430 let handle = self.handle;
431 debug!(
432 "SQLGetConnectAttr called with attribute '{attribute:?}' for connection \
433 '{handle:?}' reported '{out}'."
434 );
435 out
436 })
437 }
438}