odbc_api/handles/
connection.rs

1use super::{
2    as_handle::AsHandle,
3    buffer::mut_buf_ptr,
4    drop_handle,
5    sql_char::{
6        binary_length, is_truncated_bin, resize_to_fit_with_tz, resize_to_fit_without_tz, SqlChar,
7        SqlText,
8    },
9    sql_result::ExtSqlReturn,
10    statement::StatementImpl,
11    OutputStringBuffer, SqlResult,
12};
13use log::debug;
14use odbc_sys::{
15    CompletionType, ConnectionAttribute, DriverConnectOption, HDbc, HEnv, HStmt, HWnd, Handle,
16    HandleType, InfoType, Pointer, SQLAllocHandle, SQLDisconnect, SQLEndTran, IS_UINTEGER,
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        sql_driver_connect(
161            self.handle,
162            parent_window,
163            connection_string.ptr(),
164            connection_string.len_char().try_into().unwrap(),
165            completed_connection_string.mut_buf_ptr(),
166            completed_connection_string.buf_len(),
167            completed_connection_string.mut_actual_len_ptr(),
168            driver_completion,
169        )
170        .into_sql_result("SQLDriverConnect")
171    }
172
173    /// Disconnect from an ODBC data source.
174    pub fn disconnect(&mut self) -> SqlResult<()> {
175        unsafe { SQLDisconnect(self.handle).into_sql_result("SQLDisconnect") }
176    }
177
178    /// Allocate a new statement handle. The `Statement` must not outlive the `Connection`.
179    pub fn allocate_statement(&self) -> SqlResult<StatementImpl<'_>> {
180        let mut out = null_mut();
181        unsafe {
182            SQLAllocHandle(HandleType::Stmt, self.as_handle(), &mut out)
183                .into_sql_result("SQLAllocHandle")
184                .on_success(|| StatementImpl::new(out as HStmt))
185        }
186    }
187
188    /// Specify the transaction mode. By default, ODBC transactions are in auto-commit mode (unless
189    /// SQLSetConnectAttr and SQLSetConnectOption are not supported, which is unlikely). Switching
190    /// from manual-commit mode to auto-commit mode automatically commits any open transaction on
191    /// the connection.
192    pub fn set_autocommit(&self, enabled: bool) -> SqlResult<()> {
193        let val = enabled as u32;
194        unsafe {
195            sql_set_connect_attr(
196                self.handle,
197                ConnectionAttribute::AutoCommit,
198                val as Pointer,
199                0, // will be ignored according to ODBC spec
200            )
201            .into_sql_result("SQLSetConnectAttr")
202        }
203    }
204
205    /// Number of seconds to wait for a login request to complete before returning to the
206    /// application. The default is driver-dependent. If `0` the timeout is dasabled and a
207    /// connection attempt will wait indefinitely.
208    ///
209    /// If the specified timeout exceeds the maximum login timeout in the data source, the driver
210    /// substitutes that value and uses the maximum login timeout instead.
211    ///
212    /// This corresponds to the `SQL_ATTR_LOGIN_TIMEOUT` attribute in the ODBC specification.
213    ///
214    /// See:
215    /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
216    pub fn set_login_timeout_sec(&self, timeout: u32) -> SqlResult<()> {
217        unsafe {
218            sql_set_connect_attr(
219                self.handle,
220                ConnectionAttribute::LoginTimeout,
221                timeout as Pointer,
222                0,
223            )
224            .into_sql_result("SQLSetConnectAttr")
225        }
226    }
227
228    /// Specifying the network packet size in bytes. Note: Many data sources either do not support
229    /// this option or only can return but not set the network packet size. If the specified size
230    /// exceeds the maximum packet size or is smaller than the minimum packet size, the driver
231    /// substitutes that value and returns SQLSTATE 01S02 (Option value changed). If the application
232    /// sets packet size after a connection has already been made, the driver will return SQLSTATE
233    /// HY011 (Attribute cannot be set now).
234    ///
235    /// See:
236    /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
237    pub fn set_packet_size(&self, packet_size: u32) -> SqlResult<()> {
238        unsafe {
239            sql_set_connect_attr(
240                self.handle,
241                ConnectionAttribute::PacketSize,
242                packet_size as Pointer,
243                0,
244            )
245            .into_sql_result("SQLSetConnectAttr")
246        }
247    }
248
249    /// To commit a transaction in manual-commit mode.
250    pub fn commit(&self) -> SqlResult<()> {
251        unsafe {
252            SQLEndTran(HandleType::Dbc, self.as_handle(), CompletionType::Commit)
253                .into_sql_result("SQLEndTran")
254        }
255    }
256
257    /// Roll back a transaction in manual-commit mode.
258    pub fn rollback(&self) -> SqlResult<()> {
259        unsafe {
260            SQLEndTran(HandleType::Dbc, self.as_handle(), CompletionType::Rollback)
261                .into_sql_result("SQLEndTran")
262        }
263    }
264
265    /// Fetch the name of the database management system used by the connection and store it into
266    /// the provided `buf`.
267    pub fn fetch_database_management_system_name(&self, buf: &mut Vec<SqlChar>) -> SqlResult<()> {
268        // String length in bytes, not characters. Terminating zero is excluded.
269        let mut string_length_in_bytes: i16 = 0;
270        // Let's utilize all of `buf`s capacity.
271        buf.resize(buf.capacity(), 0);
272
273        unsafe {
274            let mut res = sql_get_info(
275                self.handle,
276                InfoType::DbmsName,
277                mut_buf_ptr(buf) as Pointer,
278                binary_length(buf).try_into().unwrap(),
279                &mut string_length_in_bytes as *mut i16,
280            )
281            .into_sql_result("SQLGetInfo");
282
283            if res.is_err() {
284                return res;
285            }
286
287            // Call has been a success but let's check if the buffer had been large enough.
288            if is_truncated_bin(buf, string_length_in_bytes.try_into().unwrap()) {
289                // It seems we must try again with a large enough buffer.
290                resize_to_fit_with_tz(buf, string_length_in_bytes.try_into().unwrap());
291                res = sql_get_info(
292                    self.handle,
293                    InfoType::DbmsName,
294                    mut_buf_ptr(buf) as Pointer,
295                    binary_length(buf).try_into().unwrap(),
296                    &mut string_length_in_bytes as *mut i16,
297                )
298                .into_sql_result("SQLGetInfo");
299
300                if res.is_err() {
301                    return res;
302                }
303            }
304
305            // Resize buffer to exact string length without terminal zero
306            resize_to_fit_without_tz(buf, string_length_in_bytes.try_into().unwrap());
307            res
308        }
309    }
310
311    fn info_u16(&self, info_type: InfoType) -> SqlResult<u16> {
312        unsafe {
313            let mut value = 0u16;
314            sql_get_info(
315                self.handle,
316                info_type,
317                &mut value as *mut u16 as Pointer,
318                // Buffer length should not be required in this case, according to the ODBC
319                // documentation at https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetinfo-function?view=sql-server-ver15#arguments
320                // However, in practice some drivers (such as Microsoft Access) require it to be
321                // specified explicitly here, otherwise they return an error without diagnostics.
322                size_of::<*mut u16>() as i16,
323                null_mut(),
324            )
325            .into_sql_result("SQLGetInfo")
326            .on_success(|| value)
327        }
328    }
329
330    /// Maximum length of catalog names.
331    pub fn max_catalog_name_len(&self) -> SqlResult<u16> {
332        self.info_u16(InfoType::MaxCatalogNameLen)
333    }
334
335    /// Maximum length of schema names.
336    pub fn max_schema_name_len(&self) -> SqlResult<u16> {
337        self.info_u16(InfoType::MaxSchemaNameLen)
338    }
339
340    /// Maximum length of table names.
341    pub fn max_table_name_len(&self) -> SqlResult<u16> {
342        self.info_u16(InfoType::MaxTableNameLen)
343    }
344
345    /// Maximum length of column names.
346    pub fn max_column_name_len(&self) -> SqlResult<u16> {
347        self.info_u16(InfoType::MaxColumnNameLen)
348    }
349
350    /// Fetch the name of the current catalog being used by the connection and store it into the
351    /// provided `buf`.
352    pub fn fetch_current_catalog(&self, buffer: &mut Vec<SqlChar>) -> SqlResult<()> {
353        // String length in bytes, not characters. Terminating zero is excluded.
354        let mut string_length_in_bytes: i32 = 0;
355        // Let's utilize all of `buf`s capacity.
356        buffer.resize(buffer.capacity(), 0);
357
358        unsafe {
359            let mut res = sql_get_connect_attr(
360                self.handle,
361                ConnectionAttribute::CurrentCatalog,
362                mut_buf_ptr(buffer) as Pointer,
363                binary_length(buffer).try_into().unwrap(),
364                &mut string_length_in_bytes as *mut i32,
365            )
366            .into_sql_result("SQLGetConnectAttr");
367
368            if res.is_err() {
369                return res;
370            }
371
372            if is_truncated_bin(buffer, string_length_in_bytes.try_into().unwrap()) {
373                resize_to_fit_with_tz(buffer, string_length_in_bytes.try_into().unwrap());
374                res = sql_get_connect_attr(
375                    self.handle,
376                    ConnectionAttribute::CurrentCatalog,
377                    mut_buf_ptr(buffer) as Pointer,
378                    binary_length(buffer).try_into().unwrap(),
379                    &mut string_length_in_bytes as *mut i32,
380                )
381                .into_sql_result("SQLGetConnectAttr");
382            }
383
384            if res.is_err() {
385                return res;
386            }
387
388            // Resize buffer to exact string length without terminal zero
389            resize_to_fit_without_tz(buffer, string_length_in_bytes.try_into().unwrap());
390            res
391        }
392    }
393
394    /// Indicates the state of the connection. If `true` the connection has been lost. If `false`,
395    /// the connection is still active.
396    pub fn is_dead(&self) -> SqlResult<bool> {
397        unsafe {
398            self.attribute_u32(ConnectionAttribute::ConnectionDead)
399                .map(|v| match v {
400                    0 => false,
401                    1 => true,
402                    other => panic!("Unexpected result value from SQLGetConnectAttr: {other}"),
403                })
404        }
405    }
406
407    /// Networ packet size in bytes.
408    pub fn packet_size(&self) -> SqlResult<u32> {
409        unsafe { self.attribute_u32(ConnectionAttribute::PacketSize) }
410    }
411
412    /// # Safety
413    ///
414    /// Caller must ensure connection attribute is numeric.
415    unsafe fn attribute_u32(&self, attribute: ConnectionAttribute) -> SqlResult<u32> {
416        let mut out: u32 = 0;
417        sql_get_connect_attr(
418            self.handle,
419            attribute,
420            &mut out as *mut u32 as *mut c_void,
421            IS_UINTEGER,
422            null_mut(),
423        )
424        .into_sql_result("SQLGetConnectAttr")
425        .on_success(|| {
426            let handle = self.handle;
427            debug!(
428                "SQLGetConnectAttr called with attribute '{attribute:?}' for connection \
429                '{handle:?}' reported '{out}'."
430            );
431            out
432        })
433    }
434}