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}