odbc_api/environment.rs
1use std::{
2 cmp::max,
3 collections::HashMap,
4 ptr::null_mut,
5 sync::{Mutex, OnceLock},
6};
7
8use crate::{
9 Connection, DriverCompleteOption, Error,
10 connection::ConnectionOptions,
11 error::ExtendResult,
12 handles::{
13 self, OutputStringBuffer, SqlChar, SqlResult, SqlText, State, SzBuffer, log_diagnostics,
14 slice_to_utf8,
15 },
16};
17use log::debug;
18use odbc_sys::{AttrCpMatch, AttrOdbcVersion, FetchOrientation, HWnd};
19
20#[cfg(all(target_os = "windows", feature = "prompt"))]
21// Currently only windows driver manager supports prompt.
22use winit::{
23 application::ApplicationHandler,
24 event::WindowEvent,
25 event_loop::{ActiveEventLoop, EventLoop},
26 platform::run_on_demand::EventLoopExtRunOnDemand,
27 window::{Window, WindowId},
28};
29
30#[cfg(feature = "odbc_version_3_80")]
31const ODBC_API_VERSION: AttrOdbcVersion = AttrOdbcVersion::Odbc3_80;
32
33#[cfg(not(feature = "odbc_version_3_80"))]
34const ODBC_API_VERSION: AttrOdbcVersion = AttrOdbcVersion::Odbc3;
35
36#[cfg(all(feature = "odbc_version_3_80", feature = "odbc_version_3_5"))]
37compile_error!("odbc_version_3_80 and odbc_version_3_5 must not both be enabled at the same time.");
38
39/// An ODBC 3.8 environment.
40///
41/// Associated with an `Environment` is any information that is global in nature, such as:
42///
43/// * The `Environment`'s state
44/// * The current environment-level diagnostics
45/// * The handles of connections currently allocated on the environment
46/// * The current stetting of each environment attribute
47///
48/// Creating the environment is the first applications do, then interacting with an ODBC driver
49/// manager. There must only be one environment in the entire process.
50#[derive(Debug)]
51pub struct Environment {
52 environment: handles::Environment,
53 /// ODBC environments use interior mutability to maintain iterator state then iterating over
54 /// driver and / or data source information. The environment is otherwise protected by interior
55 /// synchronization mechanism, yet in order to be able to access to iterate over information
56 /// using a shared reference we need to protect the interior iteration state with a mutex of
57 /// its own.
58 /// The environment is also mutable with regards to Errors, which are accessed over the handle.
59 /// If multiple fallible operations are executed in parallel, we need the mutex to ensure the
60 /// errors are fetched by the correct thread.
61 internal_state: Mutex<()>,
62}
63
64unsafe impl Sync for Environment {}
65
66impl Environment {
67 /// Enable or disable (default) connection pooling for ODBC connections. Call this function
68 /// before creating the ODBC environment for which you want to enable connection pooling.
69 ///
70 /// ODBC specifies an interface to enable the driver manager to enable connection pooling for
71 /// your application. It is off by default, but if you use ODBC to connect to your data source
72 /// instead of implementing it in your application, or importing a library you may simply enable
73 /// it in ODBC instead.
74 /// Connection Pooling is governed by two attributes. The most important one is the connection
75 /// pooling scheme which is `Off` by default. It must be set even before you create your ODBC
76 /// environment. It is global mutable state on the process level. Setting it in Rust is
77 /// therefore unsafe.
78 ///
79 /// The other one is changed via [`Self::set_connection_pooling_matching`]. It governs how a
80 /// connection is choosen from the pool. It defaults to strict which means the `Connection` you
81 /// get from the pool will have exactly the attributes specified in the connection string.
82 ///
83 /// See:
84 /// <https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/driver-manager-connection-pooling>
85 ///
86 /// # Example
87 ///
88 /// ```no_run
89 /// use odbc_api::{Environment, sys::{AttrConnectionPooling, AttrCpMatch}};
90 ///
91 /// /// Create an environment with connection pooling enabled.
92 /// let env = unsafe {
93 /// Environment::set_connection_pooling(AttrConnectionPooling::DriverAware).unwrap();
94 /// let mut env = Environment::new().unwrap();
95 /// // Strict is the default, and is set here to be explicit about it.
96 /// env.set_connection_pooling_matching(AttrCpMatch::Strict).unwrap();
97 /// env
98 /// };
99 /// ```
100 ///
101 /// # Safety
102 ///
103 /// > An ODBC driver must be fully thread-safe, and connections must not have thread affinity to
104 /// > support connection pooling. This means the driver is able to handle a call on any thread
105 /// > at any time and is able to connect on one thread, to use the connection on another thread,
106 /// > and to disconnect on a third thread.
107 ///
108 /// Also note that this is changes global mutable state for the entire process. As such it is
109 /// vulnerable to race conditions if called from more than one place in your application. It is
110 /// recommened to call this in the beginning, before creating any connection.
111 pub unsafe fn set_connection_pooling(
112 scheme: odbc_sys::AttrConnectionPooling,
113 ) -> Result<(), Error> {
114 match unsafe { handles::Environment::set_connection_pooling(scheme) } {
115 SqlResult::Error { .. } => Err(Error::FailedSettingConnectionPooling),
116 SqlResult::Success(()) | SqlResult::SuccessWithInfo(()) => Ok(()),
117 other => {
118 panic!("Unexpected return value `{other:?}`.")
119 }
120 }
121 }
122
123 /// Determines how a connection is chosen from a connection pool. When [`Self::connect`],
124 /// [`Self::connect_with_connection_string`] or [`Self::driver_connect`] is called, the Driver
125 /// Manager determines which connection is reused from the pool. The Driver Manager tries to
126 /// match the connection options in the call and the connection attributes set by the
127 /// application to the keywords and connection attributes of the connections in the pool. The
128 /// value of this attribute determines the level of precision of the matching criteria.
129 ///
130 /// The following values are used to set the value of this attribute:
131 ///
132 /// * [`crate::sys::AttrCpMatch::Strict`] = Only connections that exactly match the connection
133 /// options in the call and the connection attributes set by the application are reused. This
134 /// is the default.
135 /// * [`crate::sys::AttrCpMatch::Relaxed`] = Connections with matching connection string \
136 /// keywords can be used. Keywords must match, but not all connection attributes must match.
137 pub fn set_connection_pooling_matching(&mut self, matching: AttrCpMatch) -> Result<(), Error> {
138 self.environment
139 .set_connection_pooling_matching(matching)
140 .into_result(&self.environment)
141 }
142
143 /// Entry point into this API. Allocates a new ODBC Environment and declares to the driver
144 /// manager that the Application wants to use ODBC version 3.8.
145 ///
146 /// # Safety
147 ///
148 /// There may only be one ODBC environment in any process at any time. Take care using this
149 /// function in unit tests, as these run in parallel by default in Rust. Also no library should
150 /// probably wrap the creation of an odbc environment into a safe function call. This is because
151 /// using two of these "safe" libraries at the same time in different parts of your program may
152 /// lead to race condition thus violating Rust's safety guarantees.
153 ///
154 /// Creating one environment in your binary is safe however.
155 pub fn new() -> Result<Self, Error> {
156 let result = handles::Environment::new();
157
158 let environment = match result {
159 SqlResult::Success(env) => env,
160 SqlResult::SuccessWithInfo(env) => {
161 log_diagnostics(&env);
162 env
163 }
164 SqlResult::Error { .. } => return Err(Error::FailedAllocatingEnvironment),
165 other => panic!("Unexpected return value '{other:?}'"),
166 };
167
168 debug!("ODBC Environment created.");
169
170 debug!("Setting ODBC API version to {ODBC_API_VERSION:?}");
171 let result = environment
172 .declare_version(ODBC_API_VERSION)
173 .into_result(&environment);
174
175 // Status code S1009 has been seen with unixODBC 2.3.1. S1009 meant (among other things)
176 // invalid attribute. If we see this then we try to declare the ODBC version it is of course
177 // likely that the driver manager only knows ODBC 2.x.
178 // See: <https://learn.microsoft.com/sql/odbc/reference/develop-app/sqlstate-mappings>
179 const ODBC_2_INVALID_ATTRIBUTE: State = State(*b"S1009");
180
181 // Translate invalid attribute into a more meaningful error, provided the additional
182 // context that we know we tried to set version number.
183 result.provide_context_for_diagnostic(|record, function| match record.state {
184 // INVALID_STATE_TRANSACTION has been seen with some really old version of unixODBC on
185 // a CentOS used to build manylinux wheels, with the preinstalled ODBC version.
186 // INVALID_ATTRIBUTE_VALUE is the correct status code to emit for a driver manager if it
187 // does not know the version and has been seen with an unknown version of unixODBC on an
188 // Oracle Linux.
189 ODBC_2_INVALID_ATTRIBUTE
190 | State::INVALID_STATE_TRANSACTION
191 | State::INVALID_ATTRIBUTE_VALUE => Error::UnsupportedOdbcApiVersion(record),
192 _ => Error::Diagnostics { record, function },
193 })?;
194
195 Ok(Self {
196 environment,
197 internal_state: Mutex::new(()),
198 })
199 }
200
201 /// Allocates a connection handle and establishes connections to a driver and a data source.
202 ///
203 /// * See [Connecting with SQLConnect][1]
204 /// * See [SQLConnectFunction][2]
205 ///
206 /// # Arguments
207 ///
208 /// * `data_source_name` - Data source name. The data might be located on the same computer as
209 /// the program, or on another computer somewhere on a network.
210 /// * `user` - User identifier.
211 /// * `pwd` - Authentication string (typically the password).
212 ///
213 /// # Example
214 ///
215 /// ```no_run
216 /// use odbc_api::{Environment, ConnectionOptions};
217 ///
218 /// let env = Environment::new()?;
219 ///
220 /// let mut conn = env.connect(
221 /// "YourDatabase", "SA", "My@Test@Password1",
222 /// ConnectionOptions::default()
223 /// )?;
224 /// # Ok::<(), odbc_api::Error>(())
225 /// ```
226 ///
227 /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlconnect-function
228 /// [2]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlconnect-function
229 pub fn connect(
230 &self,
231 data_source_name: &str,
232 user: &str,
233 pwd: &str,
234 options: ConnectionOptions,
235 ) -> Result<Connection<'_>, Error> {
236 let data_source_name = SqlText::new(data_source_name);
237 let user = SqlText::new(user);
238 let pwd = SqlText::new(pwd);
239
240 let mut connection = self.allocate_connection()?;
241
242 options.apply(&connection)?;
243
244 connection
245 .connect(&data_source_name, &user, &pwd)
246 .into_result(&connection)?;
247 Ok(Connection::new(connection))
248 }
249
250 /// Allocates a connection handle and establishes connections to a driver and a data source.
251 ///
252 /// An alternative to `connect`. It supports data sources that require more connection
253 /// information than the three arguments in `connect` and data sources that are not defined in
254 /// the system information.
255 ///
256 /// To find out your connection string try: <https://www.connectionstrings.com/>
257 ///
258 /// # Example
259 ///
260 /// ```no_run
261 /// use odbc_api::{ConnectionOptions, Environment};
262 ///
263 /// let env = Environment::new()?;
264 ///
265 /// let connection_string = "
266 /// Driver={ODBC Driver 18 for SQL Server};\
267 /// Server=localhost;\
268 /// UID=SA;\
269 /// PWD=My@Test@Password1;\
270 /// ";
271 ///
272 /// let mut conn = env.connect_with_connection_string(
273 /// connection_string,
274 /// ConnectionOptions::default()
275 /// )?;
276 /// # Ok::<(), odbc_api::Error>(())
277 /// ```
278 pub fn connect_with_connection_string(
279 &self,
280 connection_string: &str,
281 options: ConnectionOptions,
282 ) -> Result<Connection<'_>, Error> {
283 let connection_string = SqlText::new(connection_string);
284 let mut connection = self.allocate_connection()?;
285
286 options.apply(&connection)?;
287
288 connection
289 .connect_with_connection_string(&connection_string)
290 .into_result(&connection)?;
291 Ok(Connection::new(connection))
292 }
293
294 /// Allocates a connection handle and establishes connections to a driver and a data source.
295 ///
296 /// An alternative to `connect` and `connect_with_connection_string`. This method can be
297 /// provided with an incomplete or even empty connection string. If any additional information
298 /// is required, the driver manager/driver will attempt to create a prompt to allow the user to
299 /// provide the additional information.
300 ///
301 /// If the connection is successful, the complete connection string (including any information
302 /// provided by the user through a prompt) is returned.
303 ///
304 /// # Parameters
305 ///
306 /// * `connection_string`: Connection string.
307 /// * `completed_connection_string`: Output buffer with the complete connection string. It is
308 /// recommended to choose a buffer with at least `1024` bytes length. **Note**: Some driver
309 /// implementation have poor error handling in case the provided buffer is too small. At the
310 /// time of this writing:
311 /// * Maria DB crashes with STATUS_TACK_BUFFER_OVERRUN
312 /// * SQLite does not change the output buffer at all and does not indicate truncation.
313 /// * `driver_completion`: Specifies how and if the driver manager uses a prompt to complete the
314 /// provided connection string. For arguments other than
315 /// [`crate::DriverCompleteOption::NoPrompt`] this method is going to create a message only
316 /// parent window for you on windows. On other platform this method is going to panic. In case
317 /// you want to provide your own parent window please use [`Self::driver_connect_with_hwnd`].
318 ///
319 /// # Examples
320 ///
321 /// In the first example, we intentionally provide a blank connection string so the user will be
322 /// prompted to select a data source to use. Note that this functionality is only available on
323 /// windows.
324 ///
325 /// ```no_run
326 /// use odbc_api::{Environment, handles::OutputStringBuffer, DriverCompleteOption};
327 ///
328 /// let env = Environment::new()?;
329 ///
330 /// let mut output_buffer = OutputStringBuffer::with_buffer_size(1024);
331 /// let connection = env.driver_connect(
332 /// "",
333 /// &mut output_buffer,
334 /// #[cfg(target_os = "windows")]
335 /// DriverCompleteOption::Prompt,
336 /// #[cfg(not(target_os = "windows"))]
337 /// DriverCompleteOption::NoPrompt,
338 /// )?;
339 ///
340 /// // Check that the output buffer has been large enough to hold the entire connection string.
341 /// assert!(!output_buffer.is_truncated());
342 ///
343 /// // Now `connection_string` will contain the data source selected by the user.
344 /// let connection_string = output_buffer.to_utf8();
345 /// # Ok::<_,odbc_api::Error>(())
346 /// ```
347 ///
348 /// In the following examples we specify a DSN that requires login credentials, but the DSN does
349 /// not provide those credentials. Instead, the user will be prompted for a UID and PWD. The
350 /// returned `connection_string` will contain the `UID` and `PWD` provided by the user. Note
351 /// that this functionality is currently only available on windows targets.
352 ///
353 /// ```
354 /// # use odbc_api::DriverCompleteOption;
355 /// # #[cfg(target_os = "windows")]
356 /// # fn f(
357 /// # mut output_buffer: odbc_api::handles::OutputStringBuffer,
358 /// # env: odbc_api::Environment,
359 /// # ) -> Result<(), odbc_api::Error> {
360 /// let without_uid_or_pwd = "DSN=SomeSharedDatabase;";
361 /// let connection = env.driver_connect(
362 /// &without_uid_or_pwd,
363 /// &mut output_buffer,
364 /// DriverCompleteOption::Complete,
365 /// )?;
366 /// let connection_string = output_buffer.to_utf8();
367 ///
368 /// // Now `connection_string` might be something like
369 /// // `DSN=SomeSharedDatabase;UID=SA;PWD=My@Test@Password1;`
370 /// # Ok(()) }
371 /// ```
372 ///
373 /// In this case, we use a DSN that is already sufficient and does not require a prompt. Because
374 /// a prompt is not needed, `window` is also not required. The returned `connection_string` will
375 /// be mostly the same as `already_sufficient` but the driver may append some extra attributes.
376 ///
377 /// ```
378 /// # use odbc_api::DriverCompleteOption;
379 /// # fn f(
380 /// # mut output_buffer: odbc_api::handles::OutputStringBuffer,
381 /// # env: odbc_api::Environment,
382 /// # ) -> Result<(), odbc_api::Error> {
383 /// let already_sufficient = "DSN=MicrosoftAccessFile;";
384 /// let connection = env.driver_connect(
385 /// &already_sufficient,
386 /// &mut output_buffer,
387 /// DriverCompleteOption::NoPrompt,
388 /// )?;
389 /// let connection_string = output_buffer.to_utf8();
390 ///
391 /// // Now `connection_string` might be something like
392 /// // `DSN=MicrosoftAccessFile;DBQ=C:\Db\Example.accdb;DriverId=25;FIL=MS Access;MaxBufferSize=2048;`
393 /// # Ok(()) }
394 /// ```
395 pub fn driver_connect(
396 &self,
397 connection_string: &str,
398 completed_connection_string: &mut OutputStringBuffer,
399 driver_completion: DriverCompleteOption,
400 ) -> Result<Connection<'_>, Error> {
401 let mut driver_connect = |hwnd: HWnd| unsafe {
402 self.driver_connect_with_hwnd(
403 connection_string,
404 completed_connection_string,
405 driver_completion,
406 hwnd,
407 )
408 };
409
410 match driver_completion {
411 DriverCompleteOption::NoPrompt => (),
412 #[cfg(all(target_os = "windows", feature = "prompt"))]
413 _ => {
414 // We need a parent window, let's provide a message only window.
415 let mut window_app = MessageOnlyWindowEventHandler {
416 run_prompt_dialog: Some(driver_connect),
417 result: None,
418 };
419 let mut event_loop = EventLoop::new().unwrap();
420 event_loop.run_app_on_demand(&mut window_app).unwrap();
421 return window_app.result.unwrap();
422 }
423 };
424 let hwnd = null_mut();
425 driver_connect(hwnd)
426 }
427
428 /// Allows to call driver connect with a user supplied HWnd. Same as [`Self::driver_connect`],
429 /// but with the possibility to provide your own parent window handle in case you want to show
430 /// a prompt to the user.
431 ///
432 /// # Safety
433 ///
434 /// `parent_window` must be a valid window handle, to a window type supported by the ODBC driver
435 /// manager. On windows this is a plain window handle, which is of course understood by the
436 /// windows built in ODBC driver manager. Other working combinations are unknown to the author.
437 pub unsafe fn driver_connect_with_hwnd(
438 &self,
439 connection_string: &str,
440 completed_connection_string: &mut OutputStringBuffer,
441 driver_completion: DriverCompleteOption,
442 parent_window: HWnd,
443 ) -> Result<Connection<'_>, Error> {
444 let mut connection = self.allocate_connection()?;
445 let connection_string = SqlText::new(connection_string);
446
447 let connection_string_is_complete = unsafe {
448 connection.driver_connect(
449 &connection_string,
450 parent_window,
451 completed_connection_string,
452 driver_completion.as_sys(),
453 )
454 }
455 .into_result_bool(&connection)?;
456 if !connection_string_is_complete {
457 return Err(Error::AbortedConnectionStringCompletion);
458 }
459 Ok(Connection::new(connection))
460 }
461
462 /// Get information about available drivers. Only 32 or 64 Bit drivers will be listed, depending
463 /// on whether you are building a 32 Bit or 64 Bit application.
464 ///
465 /// # Example
466 ///
467 /// ```no_run
468 /// use odbc_api::Environment;
469 ///
470 /// let env = Environment::new ()?;
471 /// for driver_info in env.drivers()? {
472 /// println!("{:#?}", driver_info);
473 /// }
474 ///
475 /// # Ok::<_, odbc_api::Error>(())
476 /// ```
477 pub fn drivers(&self) -> Result<Vec<DriverInfo>, Error> {
478 let mut driver_info = Vec::new();
479
480 // Since we have exclusive ownership of the environment handle and we take the lock, we can
481 // guarantee that this method is currently the only one changing the state of the internal
482 // iterators of the environment.
483 let _lock = self.internal_state.lock().unwrap();
484 unsafe {
485 // Find required buffer size to avoid truncation.
486 let (mut desc_len, mut attr_len) = if let Some(res) = self
487 .environment
488 // Start with first so we are independent of state
489 .drivers_buffer_len(FetchOrientation::First)
490 .map(Some)
491 .on_no_data(|| None)
492 .into_result(&self.environment)?
493 {
494 res
495 } else {
496 // No drivers present
497 return Ok(Vec::new());
498 };
499
500 // If there are, let's loop over the remaining drivers
501 while let Some((candidate_desc_len, candidate_attr_len)) = self
502 .environment
503 .drivers_buffer_len(FetchOrientation::Next)
504 .or_no_data()
505 .into_result(&self.environment)?
506 {
507 desc_len = max(candidate_desc_len, desc_len);
508 attr_len = max(candidate_attr_len, attr_len);
509 }
510
511 // Allocate +1 character extra for terminating zero
512 let mut desc_buf = SzBuffer::with_capacity(desc_len as usize);
513 // Do **not** use nul terminated buffer, as nul is used to delimit key value pairs of
514 // attributes.
515 let mut attr_buf: Vec<SqlChar> = vec![0; attr_len as usize];
516
517 while self
518 .environment
519 .drivers_buffer_fill(FetchOrientation::Next, desc_buf.mut_buf(), &mut attr_buf)
520 .into_result_bool(&self.environment)?
521 {
522 let description = desc_buf.to_utf8();
523 let attributes =
524 slice_to_utf8(&attr_buf).expect("Attributes must be interpretable as UTF-8");
525
526 let attributes = attributes_iter(&attributes).collect();
527
528 driver_info.push(DriverInfo {
529 description,
530 attributes,
531 });
532 }
533 }
534
535 Ok(driver_info)
536 }
537
538 /// User and system data sources
539 ///
540 /// # Example
541 ///
542 /// ```no_run
543 /// use odbc_api::Environment;
544 ///
545 /// let env = Environment::new()?;
546 /// for data_source in env.data_sources()? {
547 /// println!("{:#?}", data_source);
548 /// }
549 ///
550 /// # Ok::<_, odbc_api::Error>(())
551 /// ```
552 pub fn data_sources(&self) -> Result<Vec<DataSourceInfo>, Error> {
553 self.data_sources_impl(FetchOrientation::First)
554 }
555
556 /// Only system data sources
557 ///
558 /// # Example
559 ///
560 /// ```no_run
561 /// use odbc_api::Environment;
562 ///
563 /// let env = Environment::new ()?;
564 /// for data_source in env.system_data_sources()? {
565 /// println!("{:#?}", data_source);
566 /// }
567 ///
568 /// # Ok::<_, odbc_api::Error>(())
569 /// ```
570 pub fn system_data_sources(&self) -> Result<Vec<DataSourceInfo>, Error> {
571 self.data_sources_impl(FetchOrientation::FirstSystem)
572 }
573
574 /// Only user data sources
575 ///
576 /// # Example
577 ///
578 /// ```no_run
579 /// use odbc_api::Environment;
580 ///
581 /// let mut env = unsafe { Environment::new () }?;
582 /// for data_source in env.user_data_sources()? {
583 /// println!("{:#?}", data_source);
584 /// }
585 ///
586 /// # Ok::<_, odbc_api::Error>(())
587 /// ```
588 pub fn user_data_sources(&self) -> Result<Vec<DataSourceInfo>, Error> {
589 self.data_sources_impl(FetchOrientation::FirstUser)
590 }
591
592 fn data_sources_impl(&self, direction: FetchOrientation) -> Result<Vec<DataSourceInfo>, Error> {
593 let mut data_source_info = Vec::new();
594
595 // Since we have exclusive ownership of the environment handle and we take the lock, we can
596 // guarantee that this method is currently the only one changing the state of the internal
597 // iterators of the environment.
598 let _lock = self.internal_state.lock().unwrap();
599 unsafe {
600 // Find required buffer size to avoid truncation.
601 let (mut server_name_len, mut driver_len) = if let Some(res) = self
602 .environment
603 .data_source_buffer_len(direction)
604 .or_no_data()
605 .into_result(&self.environment)?
606 {
607 res
608 } else {
609 // No drivers present
610 return Ok(Vec::new());
611 };
612
613 // If there are let's loop over the rest
614 while let Some((candidate_name_len, candidate_decs_len)) = self
615 .environment
616 .drivers_buffer_len(FetchOrientation::Next)
617 .or_no_data()
618 .into_result(&self.environment)?
619 {
620 server_name_len = max(candidate_name_len, server_name_len);
621 driver_len = max(candidate_decs_len, driver_len);
622 }
623
624 let mut server_name_buf = SzBuffer::with_capacity(server_name_len as usize);
625 let mut driver_buf = SzBuffer::with_capacity(driver_len as usize);
626
627 let mut not_empty = self
628 .environment
629 .data_source_buffer_fill(direction, server_name_buf.mut_buf(), driver_buf.mut_buf())
630 .into_result_bool(&self.environment)?;
631
632 while not_empty {
633 let server_name = server_name_buf.to_utf8();
634 let driver = driver_buf.to_utf8();
635
636 data_source_info.push(DataSourceInfo {
637 server_name,
638 driver,
639 });
640 not_empty = self
641 .environment
642 .data_source_buffer_fill(
643 FetchOrientation::Next,
644 server_name_buf.mut_buf(),
645 driver_buf.mut_buf(),
646 )
647 .into_result_bool(&self.environment)?;
648 }
649 }
650
651 Ok(data_source_info)
652 }
653
654 fn allocate_connection(&self) -> Result<handles::Connection<'_>, Error> {
655 // Hold lock diagnostics errors are consumed in this thread.
656 let _lock = self.internal_state.lock().unwrap();
657 self.environment
658 .allocate_connection()
659 .into_result(&self.environment)
660 }
661}
662
663/// An ODBC [`Environment`] with static lifetime. This function always returns a reference to the
664/// same instance. The environment is constructed then the function is called for the first time.
665/// Every time after the initial construction this function must succeed.
666///
667/// Useful if your application uses ODBC for the entirety of its lifetime, since using a static
668/// lifetime means there is one less lifetime you and the borrow checker need to worry about. If
669/// your application only wants to use odbc for part of its runtime, you may want to use
670/// [`Environment`] directly in order to explicitly free its associated resources earlier. No matter
671/// the application, it is recommended to only have one [`Environment`] per process.
672pub fn environment() -> Result<&'static Environment, Error> {
673 static ENV: OnceLock<Environment> = OnceLock::new();
674 if let Some(env) = ENV.get() {
675 // Environment already initialized, nothing to do, but to return it.
676 Ok(env)
677 } else {
678 // ODBC Environment not initialized yet. Let's do so and return it.
679 let env = Environment::new()?;
680 let env = ENV.get_or_init(|| env);
681 Ok(env)
682 }
683}
684
685/// Struct holding information available on a driver. Can be obtained via [`Environment::drivers`].
686#[derive(Clone, Debug, Eq, PartialEq)]
687pub struct DriverInfo {
688 /// Name of the ODBC driver
689 pub description: String,
690 /// Attributes values of the driver by key
691 pub attributes: HashMap<String, String>,
692}
693
694/// Holds name and description of a datasource
695///
696/// Can be obtained via [`Environment::data_sources`]
697#[derive(Clone, Debug, Eq, PartialEq)]
698pub struct DataSourceInfo {
699 /// Name of the data source
700 pub server_name: String,
701 /// Description of the data source
702 pub driver: String,
703}
704
705/// Message loop for prompt dialog. Used by [`Environment::driver_connect`].
706#[cfg(all(target_os = "windows", feature = "prompt"))]
707struct MessageOnlyWindowEventHandler<'a, F> {
708 run_prompt_dialog: Option<F>,
709 result: Option<Result<Connection<'a>, Error>>,
710}
711
712#[cfg(all(target_os = "windows", feature = "prompt"))]
713impl<'a, F> ApplicationHandler for MessageOnlyWindowEventHandler<'a, F>
714where
715 F: FnOnce(HWnd) -> Result<Connection<'a>, Error>,
716{
717 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
718 let parent_window = event_loop
719 .create_window(Window::default_attributes().with_visible(false))
720 .unwrap();
721
722 use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle, Win32WindowHandle};
723
724 let hwnd = match parent_window.window_handle().unwrap().as_raw() {
725 RawWindowHandle::Win32(Win32WindowHandle { hwnd, .. }) => hwnd.get() as HWnd,
726 _ => panic!("ODBC Prompt is only supported on window platforms"),
727 };
728
729 if let Some(run_dialog) = self.run_prompt_dialog.take() {
730 self.result = Some(run_dialog(hwnd))
731 }
732 event_loop.exit();
733 }
734
735 fn window_event(&mut self, _event_loop: &ActiveEventLoop, _id: WindowId, _event: WindowEvent) {}
736}
737
738/// Called by drivers to pares list of attributes
739///
740/// Key value pairs are separated by `\0`. Key and value are separated by `=`
741fn attributes_iter(attributes: &str) -> impl Iterator<Item = (String, String)> + '_ {
742 attributes
743 .split('\0')
744 .take_while(|kv_str| *kv_str != String::new())
745 .map(|kv_str| {
746 let mut iter = kv_str.split('=');
747 let key = iter.next().unwrap();
748 let value = iter.next().unwrap();
749 (key.to_string(), value.to_string())
750 })
751}
752
753#[cfg(test)]
754mod tests {
755
756 use super::*;
757
758 #[test]
759 fn parse_attributes() {
760 let buffer = "APILevel=2\0ConnectFunctions=YYY\0CPTimeout=60\0DriverODBCVer=03.\
761 50\0FileUsage=0\0SQLLevel=1\0UsageCount=1\0\0";
762 let attributes: HashMap<_, _> = attributes_iter(buffer).collect();
763 assert_eq!(attributes["APILevel"], "2");
764 assert_eq!(attributes["ConnectFunctions"], "YYY");
765 assert_eq!(attributes["CPTimeout"], "60");
766 assert_eq!(attributes["DriverODBCVer"], "03.50");
767 assert_eq!(attributes["FileUsage"], "0");
768 assert_eq!(attributes["SQLLevel"], "1");
769 assert_eq!(attributes["UsageCount"], "1");
770 }
771}