Skip to main content

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, WHITESPACE, 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
336                // If a dbms does not support delemiting identifiers the driver is suppossed to
337                // report a single whitespace.
338                if buf[0] == WHITESPACE {
339                    None
340                } else {
341                    char::from_u32(u32::from(buf[0]))
342                }
343            })
344        }
345    }
346
347    fn info_u16(&self, info_type: InfoType) -> SqlResult<u16> {
348        unsafe {
349            let mut value = 0u16;
350            sql_get_info(
351                self.handle,
352                info_type,
353                &mut value as *mut u16 as Pointer,
354                // Buffer length should not be required in this case, according to the ODBC
355                // documentation at https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetinfo-function?view=sql-server-ver15#arguments
356                // However, in practice some drivers (such as Microsoft Access) require it to be
357                // specified explicitly here, otherwise they return an error without diagnostics.
358                size_of::<*mut u16>() as i16,
359                null_mut(),
360            )
361            .into_sql_result("SQLGetInfo")
362            .on_success(|| value)
363        }
364    }
365
366    /// Maximum length of catalog names.
367    pub fn max_catalog_name_len(&self) -> SqlResult<u16> {
368        self.info_u16(InfoType::MaxCatalogNameLen)
369    }
370
371    /// Maximum length of schema names.
372    pub fn max_schema_name_len(&self) -> SqlResult<u16> {
373        self.info_u16(InfoType::MaxSchemaNameLen)
374    }
375
376    /// Maximum length of table names.
377    pub fn max_table_name_len(&self) -> SqlResult<u16> {
378        self.info_u16(InfoType::MaxTableNameLen)
379    }
380
381    /// Maximum length of column names.
382    pub fn max_column_name_len(&self) -> SqlResult<u16> {
383        self.info_u16(InfoType::MaxColumnNameLen)
384    }
385
386    /// Fetch the name of the current catalog being used by the connection and store it into the
387    /// provided `buf`.
388    pub fn fetch_current_catalog(&self, buffer: &mut Vec<SqlChar>) -> SqlResult<()> {
389        // String length in bytes, not characters. Terminating zero is excluded.
390        let mut string_length_in_bytes: i32 = 0;
391        // Let's utilize all of `buf`s capacity.
392        buffer.resize(buffer.capacity(), 0);
393
394        unsafe {
395            let mut res = sql_get_connect_attr(
396                self.handle,
397                ConnectionAttribute::CURRENT_CATALOG,
398                mut_buf_ptr(buffer) as Pointer,
399                binary_length(buffer).try_into().unwrap(),
400                &mut string_length_in_bytes as *mut i32,
401            )
402            .into_sql_result("SQLGetConnectAttr");
403
404            if res.is_err() {
405                return res;
406            }
407
408            if is_truncated_bin(buffer, string_length_in_bytes.try_into().unwrap()) {
409                resize_to_fit_with_tz(buffer, string_length_in_bytes.try_into().unwrap());
410                res = sql_get_connect_attr(
411                    self.handle,
412                    ConnectionAttribute::CURRENT_CATALOG,
413                    mut_buf_ptr(buffer) as Pointer,
414                    binary_length(buffer).try_into().unwrap(),
415                    &mut string_length_in_bytes as *mut i32,
416                )
417                .into_sql_result("SQLGetConnectAttr");
418            }
419
420            if res.is_err() {
421                return res;
422            }
423
424            // Resize buffer to exact string length without terminal zero
425            resize_to_fit_without_tz(buffer, string_length_in_bytes.try_into().unwrap());
426            res
427        }
428    }
429
430    /// Indicates the state of the connection. If `true` the connection has been lost. If `false`,
431    /// the connection is still active.
432    pub fn is_dead(&self) -> SqlResult<bool> {
433        unsafe {
434            self.attribute_u32(ConnectionAttribute::CONNECTION_DEAD)
435                .map(|v| match v {
436                    0 => false,
437                    1 => true,
438                    other => panic!("Unexpected result value from SQLGetConnectAttr: {other}"),
439                })
440        }
441    }
442
443    /// Networ packet size in bytes.
444    pub fn packet_size(&self) -> SqlResult<u32> {
445        unsafe { self.attribute_u32(ConnectionAttribute::PACKET_SIZE) }
446    }
447
448    /// Sets a connection attribute.
449    ///
450    /// # Safety
451    ///
452    /// Connection attribute can control all kinds of behavior and change the nature of the
453    /// connection in a fundamental way. This includes wether calls are blocking or asynchronous,
454    /// wether transactions are explicit or auto-committed. On top of that, the ODBC standard allows
455    /// for drivers to specify their own connection attributes.
456    ///
457    /// The circumstances under which calling this function is safe depends on the attribute in
458    /// question. On top of that, callers must also ensure that the driver would know how to
459    /// interpret the attribute, in order for this call to be safe.
460    pub unsafe fn set_attribute(&self, attribute: impl SetConnectionAttribute) -> SqlResult<()> {
461        unsafe {
462            sql_set_connect_attr(
463                self.handle,
464                attribute.attribute(),
465                attribute.value(),
466                attribute.len(),
467            )
468            .into_sql_result("SQLSetConnectAttr")
469        }
470    }
471
472    /// # Safety
473    ///
474    /// Caller must ensure connection attribute is numeric.
475    unsafe fn attribute_u32(&self, attribute: ConnectionAttribute) -> SqlResult<u32> {
476        let mut out: u32 = 0;
477        unsafe {
478            sql_get_connect_attr(
479                self.handle,
480                attribute,
481                &mut out as *mut u32 as *mut c_void,
482                IS_UINTEGER,
483                null_mut(),
484            )
485        }
486        .into_sql_result("SQLGetConnectAttr")
487        .on_success(|| {
488            let handle = self.handle;
489            #[cfg(not(feature = "structured_logging"))]
490            trace!(
491                "SQLGetConnectAttr called with attribute '{attribute:?}' for connection \
492                '{handle:?}' reported '{out}'."
493            );
494            #[cfg(feature = "structured_logging")]
495            trace!(
496                target: "odbc_api",
497                attribute:? = attribute,
498                handle:? = handle,
499                value = out;
500                "Connection attribute queried"
501            );
502            out
503        })
504    }
505}
506
507/// Indicates that the implementer is can be set as a Connection Attribute. This trait is
508/// implemented for both, attributes which are set after the connection is created, as well as
509/// attributes which are set before connecting.
510///
511/// Users of `odbc-api` usually would not want to implement this themselves and rather use safe
512/// abstractions around setting connection attributes. E.g. providing [`crate::ConnectionOptions`] on
513/// connecting.
514///
515/// A reason to implement this trait in your applicaction code however, could be that you want to
516/// set an attribute which is not part of the ODBC standard, but only supported by your specific
517/// driver, which you happen to know your application will use.
518///
519/// See: <https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/connection-attributes>
520///
521/// # Safety
522///
523/// Implementers must take care that the results of [`Self::value`] and [`Self::len`] match the
524/// expectations of the ODBC driver for the given `attribute`.
525pub unsafe trait SetConnectionAttribute {
526    /// The Connection Attribute to set.
527    fn attribute(&self) -> ConnectionAttribute;
528
529    /// The interpretation of the returned pointer depends on the value returned by `attribute`.
530    ///
531    /// The value to be set. Depending on `attribute` the pointer value might be directly
532    /// interpreted as an integer value without being dereferenced. If `attribute` is represented as
533    /// text then pointer points to a buffer containing the text.
534    fn value(&self) -> Pointer;
535
536    /// Implementers please see
537    /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function> in
538    /// order to decide on the correct output for this function
539    fn len(&self) -> i32;
540}
541
542struct PacketSizeConnectionAttribute(pub u32);
543
544unsafe impl SetConnectionAttribute for PacketSizeConnectionAttribute {
545    fn attribute(&self) -> ConnectionAttribute {
546        ConnectionAttribute::PACKET_SIZE
547    }
548
549    fn value(&self) -> Pointer {
550        self.0 as Pointer
551    }
552
553    fn len(&self) -> i32 {
554        IS_UINTEGER // Ignored for integer attributes
555    }
556}
557
558struct LoginTimeoutConnectionAttribute(pub u32);
559
560unsafe impl SetConnectionAttribute for LoginTimeoutConnectionAttribute {
561    fn attribute(&self) -> ConnectionAttribute {
562        ConnectionAttribute::LOGIN_TIMEOUT
563    }
564
565    fn value(&self) -> Pointer {
566        self.0 as Pointer
567    }
568
569    fn len(&self) -> i32 {
570        IS_UINTEGER // Ignored for integer attributes
571    }
572}
573
574struct AutocommitConnectionAttribute(pub bool);
575
576unsafe impl SetConnectionAttribute for AutocommitConnectionAttribute {
577    fn attribute(&self) -> ConnectionAttribute {
578        ConnectionAttribute::AUTOCOMMIT
579    }
580
581    fn value(&self) -> Pointer {
582        (self.0 as u32) as Pointer
583    }
584
585    fn len(&self) -> i32 {
586        IS_UINTEGER // Ignored for integer attributes
587    }
588}