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}