odbc_api/handles/connection.rs
1use super::{
2 OutputStringBuffer, SqlResult,
3 any_handle::AnyHandle,
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::trace;
14use odbc_sys::{
15 CompletionType, ConnectionAttribute, DriverConnectOption, HDbc, HEnv, HWnd, Handle, HandleType,
16 IS_UINTEGER, InfoType, Pointer, SQLAllocHandle, SQLDisconnect, SQLEndTran,
17};
18use std::{cmp::max, 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 AnyHandle 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/// Operations to a connection imply that interior state of the connection might be mutated, yet we
67/// use a `&self` reference for most methods rather than `&mut self`. This means [`Connection`] must
68/// not be `Sync`. `Send` however is fine, due to the guarantees given by the ODBC interface.
69/// However, there might be a difference, between what ODBC demands from drivers and how they are
70/// actually implemented. However, even in practice the situation seems to have improved enuough to
71/// allow for [`Connection`]s to be regarded as `Send` without alerting the author of the ODBC
72/// application. If the driver has a bug, it is just that.
73///
74/// In addition to that, this has not caused trouble in a while. So we mark sending connections to
75/// other threads as safe. Reading through the documentation, one might get the impression that
76/// Connections are also `Sync`. This could be theoretically true on the level of the handle, but at
77/// the latest once the interior mutability due to error handling comes in to play, higher level
78/// abstraction have to content themselves with `Send`. This is currently how far my trust with most
79/// ODBC drivers.
80///
81/// Note to users of `unixodbc`: You may configure the threading level to make unixodbc
82/// synchronize access to the driver (and thereby making them thread safe if they are not thread
83/// safe by themself. This may however hurt your performance if the driver would actually be able to
84/// perform operations in parallel.
85///
86/// See:
87/// <https://stackoverflow.com/questions/4207458/using-unixodbc-in-a-multithreaded-concurrent-setting>
88unsafe impl Send for Connection<'_> {}
89
90impl Connection<'_> {
91 /// # Safety
92 ///
93 /// Call this method only with a valid (successfully allocated) ODBC connection handle.
94 pub unsafe fn new(handle: HDbc) -> Self {
95 Self {
96 handle,
97 parent: PhantomData,
98 }
99 }
100
101 /// Directly acces the underlying ODBC handle.
102 pub fn as_sys(&self) -> HDbc {
103 self.handle
104 }
105
106 /// Establishes connections to a driver and a data source.
107 ///
108 /// * See [Connecting with SQLConnect][1]
109 /// * See [SQLConnectFunction][2]
110 ///
111 /// # Arguments
112 ///
113 /// * `data_source_name` - Data source name. The data might be located on the same computer as
114 /// the program, or on another computer somewhere on a network.
115 /// * `user` - User identifier.
116 /// * `pwd` - Authentication string (typically the password).
117 ///
118 /// [1]: https://docs.microsoft.com//sql/odbc/reference/develop-app/connecting-with-sqlconnect
119 /// [2]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlconnect-function
120 pub fn connect(
121 &mut self,
122 data_source_name: &SqlText,
123 user: &SqlText,
124 pwd: &SqlText,
125 ) -> SqlResult<()> {
126 unsafe {
127 sql_connect(
128 self.handle,
129 data_source_name.ptr(),
130 data_source_name.len_char().try_into().unwrap(),
131 user.ptr(),
132 user.len_char().try_into().unwrap(),
133 pwd.ptr(),
134 pwd.len_char().try_into().unwrap(),
135 )
136 .into_sql_result("SQLConnect")
137 }
138 }
139
140 /// An alternative to `connect`. It supports data sources that require more connection
141 /// information than the three arguments in `connect` and data sources that are not defined in
142 /// the system information.
143 pub fn connect_with_connection_string(&mut self, connection_string: &SqlText) -> SqlResult<()> {
144 unsafe {
145 let parent_window = null_mut();
146 let mut completed_connection_string = OutputStringBuffer::empty();
147
148 self.driver_connect(
149 connection_string,
150 parent_window,
151 &mut completed_connection_string,
152 DriverConnectOption::NoPrompt,
153 )
154 // Since we did pass NoPrompt we know the user can not abort the prompt.
155 .map(|_connection_string_is_complete| ())
156 }
157 }
158
159 /// An alternative to `connect` for connecting with a connection string. Allows for completing
160 /// a connection string with a GUI prompt on windows.
161 ///
162 /// # Return
163 ///
164 /// [`SqlResult::NoData`] in case the prompt completing the connection string has been aborted.
165 ///
166 /// # Safety
167 ///
168 /// `parent_window` must either be a valid window handle or `NULL`.
169 pub unsafe fn driver_connect(
170 &mut self,
171 connection_string: &SqlText,
172 parent_window: HWnd,
173 completed_connection_string: &mut OutputStringBuffer,
174 driver_completion: DriverConnectOption,
175 ) -> SqlResult<()> {
176 unsafe {
177 sql_driver_connect(
178 self.handle,
179 parent_window,
180 connection_string.ptr(),
181 connection_string.len_char().try_into().unwrap(),
182 completed_connection_string.mut_buf_ptr(),
183 completed_connection_string.buf_len(),
184 completed_connection_string.mut_actual_len_ptr(),
185 driver_completion,
186 )
187 .into_sql_result("SQLDriverConnect")
188 }
189 }
190
191 /// Disconnect from an ODBC data source.
192 pub fn disconnect(&mut self) -> SqlResult<()> {
193 unsafe { SQLDisconnect(self.handle).into_sql_result("SQLDisconnect") }
194 }
195
196 /// Allocate a new statement handle. The `Statement` must not outlive the `Connection`.
197 pub fn allocate_statement(&self) -> SqlResult<StatementImpl<'_>> {
198 let mut out = Handle::null();
199 unsafe {
200 SQLAllocHandle(HandleType::Stmt, self.as_handle(), &mut out)
201 .into_sql_result("SQLAllocHandle")
202 .on_success(|| StatementImpl::new(out.as_hstmt()))
203 }
204 }
205
206 /// Specify the transaction mode. By default, ODBC transactions are in auto-commit mode (unless
207 /// SQLSetConnectAttr and SQLSetConnectOption are not supported, which is unlikely). Switching
208 /// from manual-commit mode to auto-commit mode automatically commits any open transaction on
209 /// the connection.
210 pub fn set_autocommit(&self, enabled: bool) -> SqlResult<()> {
211 unsafe { self.set_attribute(AutocommitConnectionAttribute(enabled)) }
212 }
213
214 /// Number of seconds to wait for a login request to complete before returning to the
215 /// application. The default is driver-dependent. If `0` the timeout is dasabled and a
216 /// connection attempt will wait indefinitely.
217 ///
218 /// If the specified timeout exceeds the maximum login timeout in the data source, the driver
219 /// substitutes that value and uses the maximum login timeout instead.
220 ///
221 /// This corresponds to the `SQL_ATTR_LOGIN_TIMEOUT` attribute in the ODBC specification.
222 ///
223 /// See:
224 /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
225 pub fn set_login_timeout_sec(&self, timeout: u32) -> SqlResult<()> {
226 unsafe { self.set_attribute(LoginTimeoutConnectionAttribute(timeout)) }
227 }
228
229 /// Specifying the network packet size in bytes. Note: Many data sources either do not support
230 /// this option or only can return but not set the network packet size. If the specified size
231 /// exceeds the maximum packet size or is smaller than the minimum packet size, the driver
232 /// substitutes that value and returns SQLSTATE 01S02 (Option value changed). If the application
233 /// sets packet size after a connection has already been made, the driver will return SQLSTATE
234 /// HY011 (Attribute cannot be set now).
235 ///
236 /// See:
237 /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
238 pub fn set_packet_size(&self, packet_size: u32) -> SqlResult<()> {
239 unsafe { self.set_attribute(PacketSizeConnectionAttribute(packet_size)) }
240 }
241
242 /// To commit a transaction in manual-commit mode.
243 pub fn commit(&self) -> SqlResult<()> {
244 unsafe {
245 SQLEndTran(HandleType::Dbc, self.as_handle(), CompletionType::Commit)
246 .into_sql_result("SQLEndTran")
247 }
248 }
249
250 /// Roll back a transaction in manual-commit mode.
251 pub fn rollback(&self) -> SqlResult<()> {
252 unsafe {
253 SQLEndTran(HandleType::Dbc, self.as_handle(), CompletionType::Rollback)
254 .into_sql_result("SQLEndTran")
255 }
256 }
257
258 /// Fetch the name of the database management system used by the connection and store it into
259 /// the provided `buf`.
260 pub fn fetch_database_management_system_name(&self, buf: &mut Vec<SqlChar>) -> SqlResult<()> {
261 // String length in bytes, not characters. Terminating zero is excluded.
262 let mut string_length_in_bytes: i16 = 0;
263
264 // We want to utilize the entire capacity of `buf` independent of its current size. There
265 // also has been an bud in the DuckDB driver not providing the length of the name if called
266 // with an empty buffer. See: <https://github.com/pacman82/odbc-api/pull/818>.
267 //
268 // Other drivers seem to only report the size correctly if the buffer is set to 0 or at
269 // already large enough to contain the entire name. These drivers then report the numbef of
270 // characters that have been returned instead of the number of characters which could have
271 // been returned. Therfore a truncation would not be detected. E.g. the linux drivers of
272 // Microsoft SQL Server and SQLite.
273 //
274 // Setting the buffer to a size large enough to likely hold the name sidesteps issues on
275 // these drivers. Mainly DuckDB though, as MSSQL and SQLite would work fine with `0`.
276 let buffer_size = max(buf.capacity(), 64);
277 buf.resize(buffer_size, 0);
278
279 unsafe {
280 let mut res = sql_get_info(
281 self.handle,
282 InfoType::DbmsName,
283 mut_buf_ptr(buf) as Pointer,
284 binary_length(buf).try_into().unwrap(),
285 &mut string_length_in_bytes as *mut i16,
286 )
287 .into_sql_result("SQLGetInfo");
288
289 if res.is_err() {
290 return res;
291 }
292
293 // Call has been a success but let's check if the buffer had been large enough.
294 if is_truncated_bin(buf, string_length_in_bytes.try_into().unwrap()) {
295 // It seems we must try again with a large enough buffer.
296 resize_to_fit_with_tz(buf, string_length_in_bytes.try_into().unwrap());
297 res = sql_get_info(
298 self.handle,
299 InfoType::DbmsName,
300 mut_buf_ptr(buf) as Pointer,
301 binary_length(buf).try_into().unwrap(),
302 &mut string_length_in_bytes as *mut i16,
303 )
304 .into_sql_result("SQLGetInfo");
305
306 if res.is_err() {
307 return res;
308 }
309 }
310
311 // Resize buffer to exact string length without terminal zero
312 resize_to_fit_without_tz(buf, string_length_in_bytes.try_into().unwrap());
313 res
314 }
315 }
316
317 /// The character used to quote identifiers (a.k.a. delimited identifiers).
318 ///
319 /// Drivers which confrom to SQL 92 standard will always return `Some('"')`.
320 pub fn identifier_quote_char(&self) -> SqlResult<Option<char>> {
321 let mut buf = [0 as SqlChar; 2];
322 let mut string_length_in_bytes: i16 = 0;
323 unsafe {
324 sql_get_info(
325 self.handle,
326 InfoType::IdentifierQuoteChar,
327 mut_buf_ptr(&mut buf) as Pointer,
328 binary_length(&buf).try_into().unwrap(),
329 &mut string_length_in_bytes as *mut i16,
330 )
331 .into_sql_result("SQLGetInfo")
332 .on_success(|| {
333 // We assume a non-zero ASCII character. Otherwise we would need different decoding
334 // for narrow and wide characters.
335 if buf[0] == 0 || buf[1] > 127 {
336 None
337 } else {
338 char::from_u32(u32::from(buf[0]))
339 }
340 })
341 }
342 }
343
344 fn info_u16(&self, info_type: InfoType) -> SqlResult<u16> {
345 unsafe {
346 let mut value = 0u16;
347 sql_get_info(
348 self.handle,
349 info_type,
350 &mut value as *mut u16 as Pointer,
351 // Buffer length should not be required in this case, according to the ODBC
352 // documentation at https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetinfo-function?view=sql-server-ver15#arguments
353 // However, in practice some drivers (such as Microsoft Access) require it to be
354 // specified explicitly here, otherwise they return an error without diagnostics.
355 size_of::<*mut u16>() as i16,
356 null_mut(),
357 )
358 .into_sql_result("SQLGetInfo")
359 .on_success(|| value)
360 }
361 }
362
363 /// Maximum length of catalog names.
364 pub fn max_catalog_name_len(&self) -> SqlResult<u16> {
365 self.info_u16(InfoType::MaxCatalogNameLen)
366 }
367
368 /// Maximum length of schema names.
369 pub fn max_schema_name_len(&self) -> SqlResult<u16> {
370 self.info_u16(InfoType::MaxSchemaNameLen)
371 }
372
373 /// Maximum length of table names.
374 pub fn max_table_name_len(&self) -> SqlResult<u16> {
375 self.info_u16(InfoType::MaxTableNameLen)
376 }
377
378 /// Maximum length of column names.
379 pub fn max_column_name_len(&self) -> SqlResult<u16> {
380 self.info_u16(InfoType::MaxColumnNameLen)
381 }
382
383 /// Fetch the name of the current catalog being used by the connection and store it into the
384 /// provided `buf`.
385 pub fn fetch_current_catalog(&self, buffer: &mut Vec<SqlChar>) -> SqlResult<()> {
386 // String length in bytes, not characters. Terminating zero is excluded.
387 let mut string_length_in_bytes: i32 = 0;
388 // Let's utilize all of `buf`s capacity.
389 buffer.resize(buffer.capacity(), 0);
390
391 unsafe {
392 let mut res = sql_get_connect_attr(
393 self.handle,
394 ConnectionAttribute::CURRENT_CATALOG,
395 mut_buf_ptr(buffer) as Pointer,
396 binary_length(buffer).try_into().unwrap(),
397 &mut string_length_in_bytes as *mut i32,
398 )
399 .into_sql_result("SQLGetConnectAttr");
400
401 if res.is_err() {
402 return res;
403 }
404
405 if is_truncated_bin(buffer, string_length_in_bytes.try_into().unwrap()) {
406 resize_to_fit_with_tz(buffer, string_length_in_bytes.try_into().unwrap());
407 res = sql_get_connect_attr(
408 self.handle,
409 ConnectionAttribute::CURRENT_CATALOG,
410 mut_buf_ptr(buffer) as Pointer,
411 binary_length(buffer).try_into().unwrap(),
412 &mut string_length_in_bytes as *mut i32,
413 )
414 .into_sql_result("SQLGetConnectAttr");
415 }
416
417 if res.is_err() {
418 return res;
419 }
420
421 // Resize buffer to exact string length without terminal zero
422 resize_to_fit_without_tz(buffer, string_length_in_bytes.try_into().unwrap());
423 res
424 }
425 }
426
427 /// Indicates the state of the connection. If `true` the connection has been lost. If `false`,
428 /// the connection is still active.
429 pub fn is_dead(&self) -> SqlResult<bool> {
430 unsafe {
431 self.attribute_u32(ConnectionAttribute::CONNECTION_DEAD)
432 .map(|v| match v {
433 0 => false,
434 1 => true,
435 other => panic!("Unexpected result value from SQLGetConnectAttr: {other}"),
436 })
437 }
438 }
439
440 /// Networ packet size in bytes.
441 pub fn packet_size(&self) -> SqlResult<u32> {
442 unsafe { self.attribute_u32(ConnectionAttribute::PACKET_SIZE) }
443 }
444
445 /// Sets a connection attribute.
446 ///
447 /// # Safety
448 ///
449 /// Connection attribute can control all kinds of behavior and change the nature of the
450 /// connection in a fundamental way. This includes wether calls are blocking or asynchronous,
451 /// wether transactions are explicit or auto-committed. On top of that, the ODBC standard allows
452 /// for drivers to specify their own connection attributes.
453 ///
454 /// The circumstances under which calling this function is safe depends on the attribute in
455 /// question. On top of that, callers must also ensure that the driver would know how to
456 /// interpret the attribute, in order for this call to be safe.
457 pub unsafe fn set_attribute(&self, attribute: impl SetConnectionAttribute) -> SqlResult<()> {
458 unsafe {
459 sql_set_connect_attr(
460 self.handle,
461 attribute.attribute(),
462 attribute.value(),
463 attribute.len(),
464 )
465 .into_sql_result("SQLSetConnectAttr")
466 }
467 }
468
469 /// # Safety
470 ///
471 /// Caller must ensure connection attribute is numeric.
472 unsafe fn attribute_u32(&self, attribute: ConnectionAttribute) -> SqlResult<u32> {
473 let mut out: u32 = 0;
474 unsafe {
475 sql_get_connect_attr(
476 self.handle,
477 attribute,
478 &mut out as *mut u32 as *mut c_void,
479 IS_UINTEGER,
480 null_mut(),
481 )
482 }
483 .into_sql_result("SQLGetConnectAttr")
484 .on_success(|| {
485 let handle = self.handle;
486 #[cfg(not(feature = "structured_logging"))]
487 trace!(
488 "SQLGetConnectAttr called with attribute '{attribute:?}' for connection \
489 '{handle:?}' reported '{out}'."
490 );
491 #[cfg(feature = "structured_logging")]
492 trace!(
493 target: "odbc_api",
494 attribute:? = attribute,
495 handle:? = handle,
496 value = out;
497 "Connection attribute queried"
498 );
499 out
500 })
501 }
502}
503
504/// Indicates that the implementer is can be set as a Connection Attribute. This trait is
505/// implemented for both, attributes which are set after the connection is created, as well as
506/// attributes which are set before connecting.
507///
508/// Users of `odbc-api` usually would not want to implement this themselves and rather use safe
509/// abstractions around setting connection attributes. E.g. providing [`crate::ConnectionOptions`] on
510/// connecting.
511///
512/// A reason to implement this trait in your applicaction code however, could be that you want to
513/// set an attribute which is not part of the ODBC standard, but only supported by your specific
514/// driver, which you happen to know your application will use.
515///
516/// See: <https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/connection-attributes>
517///
518/// # Safety
519///
520/// Implementers must take care that the results of [`Self::value`] and [`Self::len`] match the
521/// expectations of the ODBC driver for the given `attribute`.
522pub unsafe trait SetConnectionAttribute {
523 /// The Connection Attribute to set.
524 fn attribute(&self) -> ConnectionAttribute;
525
526 /// The interpretation of the returned pointer depends on the value returned by `attribute`.
527 ///
528 /// The value to be set. Depending on `attribute` the pointer value might be directly
529 /// interpreted as an integer value without being dereferenced. If `attribute` is represented as
530 /// text then pointer points to a buffer containing the text.
531 fn value(&self) -> Pointer;
532
533 /// Implementers please see
534 /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function> in
535 /// order to decide on the correct output for this function
536 fn len(&self) -> i32;
537}
538
539struct PacketSizeConnectionAttribute(pub u32);
540
541unsafe impl SetConnectionAttribute for PacketSizeConnectionAttribute {
542 fn attribute(&self) -> ConnectionAttribute {
543 ConnectionAttribute::PACKET_SIZE
544 }
545
546 fn value(&self) -> Pointer {
547 self.0 as Pointer
548 }
549
550 fn len(&self) -> i32 {
551 IS_UINTEGER // Ignored for integer attributes
552 }
553}
554
555struct LoginTimeoutConnectionAttribute(pub u32);
556
557unsafe impl SetConnectionAttribute for LoginTimeoutConnectionAttribute {
558 fn attribute(&self) -> ConnectionAttribute {
559 ConnectionAttribute::LOGIN_TIMEOUT
560 }
561
562 fn value(&self) -> Pointer {
563 self.0 as Pointer
564 }
565
566 fn len(&self) -> i32 {
567 IS_UINTEGER // Ignored for integer attributes
568 }
569}
570
571struct AutocommitConnectionAttribute(pub bool);
572
573unsafe impl SetConnectionAttribute for AutocommitConnectionAttribute {
574 fn attribute(&self) -> ConnectionAttribute {
575 ConnectionAttribute::AUTOCOMMIT
576 }
577
578 fn value(&self) -> Pointer {
579 (self.0 as u32) as Pointer
580 }
581
582 fn len(&self) -> i32 {
583 IS_UINTEGER // Ignored for integer attributes
584 }
585}