Skip to main content

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        #[cfg(not(feature = "structured_logging"))]
169        debug!("ODBC Environment created.");
170        #[cfg(feature = "structured_logging")]
171        debug!(target: "odbc_api", "ODBC environment created");
172
173        let result = environment
174            .declare_version(ODBC_API_VERSION)
175            .into_result(&environment);
176
177        // Status code S1009 has been seen with unixODBC 2.3.1. S1009 meant (among other things)
178        // invalid attribute. If we see this then we try to declare the ODBC version it is of course
179        // likely that the driver manager only knows ODBC 2.x.
180        // See: <https://learn.microsoft.com/sql/odbc/reference/develop-app/sqlstate-mappings>
181        const ODBC_2_INVALID_ATTRIBUTE: State = State(*b"S1009");
182
183        // Translate invalid attribute into a more meaningful error, provided the additional
184        // context that we know we tried to set version number.
185        result.provide_context_for_diagnostic(|record, function| match record.state {
186            // INVALID_STATE_TRANSACTION has been seen with some really old version of unixODBC on
187            // a CentOS used to build manylinux wheels, with the preinstalled ODBC version.
188            // INVALID_ATTRIBUTE_VALUE is the correct status code to emit for a driver manager if it
189            // does not know the version and has been seen with an unknown version of unixODBC on an
190            // Oracle Linux.
191            ODBC_2_INVALID_ATTRIBUTE
192            | State::INVALID_STATE_TRANSACTION
193            | State::INVALID_ATTRIBUTE_VALUE => Error::UnsupportedOdbcApiVersion(record),
194            _ => Error::Diagnostics { record, function },
195        })?;
196
197        #[cfg(not(feature = "structured_logging"))]
198        debug!("ODBC API version {ODBC_API_VERSION:?} declared");
199        #[cfg(feature = "structured_logging")]
200        debug!(
201            target: "odbc_api",
202            version:? = ODBC_API_VERSION;
203            "ODBC version declared"
204        );
205
206        Ok(Self {
207            environment,
208            internal_state: Mutex::new(()),
209        })
210    }
211
212    /// Allocates a connection handle and establishes connections to a driver and a data source.
213    ///
214    /// * See [Connecting with SQLConnect][1]
215    /// * See [SQLConnectFunction][2]
216    ///
217    /// # Arguments
218    ///
219    /// * `data_source_name` - Data source name. The data might be located on the same computer as
220    ///   the program, or on another computer somewhere on a network.
221    /// * `user` - User identifier.
222    /// * `pwd` - Authentication string (typically the password).
223    ///
224    /// # Example
225    ///
226    /// ```no_run
227    /// use odbc_api::{Environment, ConnectionOptions};
228    ///
229    /// let env = Environment::new()?;
230    ///
231    /// let mut conn = env.connect(
232    ///     "YourDatabase", "SA", "My@Test@Password1",
233    ///     ConnectionOptions::default()
234    /// )?;
235    /// # Ok::<(), odbc_api::Error>(())
236    /// ```
237    ///
238    /// [1]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlconnect-function
239    /// [2]: https://docs.microsoft.com/sql/odbc/reference/syntax/sqlconnect-function
240    pub fn connect(
241        &self,
242        data_source_name: &str,
243        user: &str,
244        pwd: &str,
245        options: ConnectionOptions,
246    ) -> Result<Connection<'_>, Error> {
247        let data_source_name = SqlText::new(data_source_name);
248        let user = SqlText::new(user);
249        let pwd = SqlText::new(pwd);
250
251        let mut connection = self.allocate_connection()?;
252
253        options.apply(&connection)?;
254
255        connection
256            .connect(&data_source_name, &user, &pwd)
257            .into_result(&connection)?;
258        Ok(Connection::new(connection))
259    }
260
261    /// Allocates a connection handle and establishes connections to a driver and a data source.
262    ///
263    /// An alternative to `connect`. It supports data sources that require more connection
264    /// information than the three arguments in `connect` and data sources that are not defined in
265    /// the system information.
266    ///
267    /// To find out your connection string try: <https://www.connectionstrings.com/>
268    ///
269    /// # Example
270    ///
271    /// ```no_run
272    /// use odbc_api::{ConnectionOptions, Environment};
273    ///
274    /// let env = Environment::new()?;
275    ///
276    /// let connection_string = "
277    ///     Driver={ODBC Driver 18 for SQL Server};\
278    ///     Server=localhost;\
279    ///     UID=SA;\
280    ///     PWD=My@Test@Password1;\
281    /// ";
282    ///
283    /// let mut conn = env.connect_with_connection_string(
284    ///     connection_string,
285    ///     ConnectionOptions::default()
286    /// )?;
287    /// # Ok::<(), odbc_api::Error>(())
288    /// ```
289    pub fn connect_with_connection_string(
290        &self,
291        connection_string: &str,
292        options: ConnectionOptions,
293    ) -> Result<Connection<'_>, Error> {
294        let connection_string = SqlText::new(connection_string);
295        let mut connection = self.allocate_connection()?;
296
297        options.apply(&connection)?;
298
299        connection
300            .connect_with_connection_string(&connection_string)
301            .into_result(&connection)?;
302        Ok(Connection::new(connection))
303    }
304
305    /// Allocates a connection handle and establishes connections to a driver and a data source.
306    ///
307    /// An alternative to `connect` and `connect_with_connection_string`. This method can be
308    /// provided with an incomplete or even empty connection string. If any additional information
309    /// is required, the driver manager/driver will attempt to create a prompt to allow the user to
310    /// provide the additional information.
311    ///
312    /// If the connection is successful, the complete connection string (including any information
313    /// provided by the user through a prompt) is returned.
314    ///
315    /// # Parameters
316    ///
317    /// * `connection_string`: Connection string.
318    /// * `completed_connection_string`: Output buffer with the complete connection string. It is
319    ///   recommended to choose a buffer with at least `1024` bytes length. **Note**: Some driver
320    ///   implementation have poor error handling in case the provided buffer is too small. At the
321    ///   time of this writing:
322    ///   * Maria DB crashes with STATUS_TACK_BUFFER_OVERRUN
323    ///   * SQLite does not change the output buffer at all and does not indicate truncation.
324    /// * `driver_completion`: Specifies how and if the driver manager uses a prompt to complete the
325    ///   provided connection string. For arguments other than
326    ///   [`crate::DriverCompleteOption::NoPrompt`] this method is going to create a message only
327    ///   parent window for you on windows. On other platform this method is going to panic. In case
328    ///   you want to provide your own parent window please use [`Self::driver_connect_with_hwnd`].
329    ///
330    /// # Examples
331    ///
332    /// In the first example, we intentionally provide a blank connection string so the user will be
333    /// prompted to select a data source to use. Note that this functionality is only available on
334    /// windows.
335    ///
336    /// ```no_run
337    /// use odbc_api::{Environment, handles::OutputStringBuffer, DriverCompleteOption};
338    ///
339    /// let env = Environment::new()?;
340    ///
341    /// let mut output_buffer = OutputStringBuffer::with_buffer_size(1024);
342    /// let connection = env.driver_connect(
343    ///     "",
344    ///     &mut output_buffer,
345    ///     #[cfg(target_os = "windows")]
346    ///     DriverCompleteOption::Prompt,
347    ///     #[cfg(not(target_os = "windows"))]
348    ///     DriverCompleteOption::NoPrompt,
349    /// )?;
350    ///
351    /// // Check that the output buffer has been large enough to hold the entire connection string.
352    /// assert!(!output_buffer.is_truncated());
353    ///
354    /// // Now `connection_string` will contain the data source selected by the user.
355    /// let connection_string = output_buffer.to_utf8();
356    /// # Ok::<_,odbc_api::Error>(())
357    /// ```
358    ///
359    /// In the following examples we specify a DSN that requires login credentials, but the DSN does
360    /// not provide those credentials. Instead, the user will be prompted for a UID and PWD. The
361    /// returned `connection_string` will contain the `UID` and `PWD` provided by the user. Note
362    /// that this functionality is currently only available on windows targets.
363    ///
364    /// ```
365    /// # use odbc_api::DriverCompleteOption;
366    /// # #[cfg(target_os = "windows")]
367    /// # fn f(
368    /// #    mut output_buffer: odbc_api::handles::OutputStringBuffer,
369    /// #    env: odbc_api::Environment,
370    /// # ) -> Result<(), odbc_api::Error> {
371    /// let without_uid_or_pwd = "DSN=SomeSharedDatabase;";
372    /// let connection = env.driver_connect(
373    ///     &without_uid_or_pwd,
374    ///     &mut output_buffer,
375    ///     DriverCompleteOption::Complete,
376    /// )?;
377    /// let connection_string = output_buffer.to_utf8();
378    ///
379    /// // Now `connection_string` might be something like
380    /// // `DSN=SomeSharedDatabase;UID=SA;PWD=My@Test@Password1;`
381    /// # Ok(()) }
382    /// ```
383    ///
384    /// In this case, we use a DSN that is already sufficient and does not require a prompt. Because
385    /// a prompt is not needed, `window` is also not required. The returned `connection_string` will
386    /// be mostly the same as `already_sufficient` but the driver may append some extra attributes.
387    ///
388    /// ```
389    /// # use odbc_api::DriverCompleteOption;
390    /// # fn f(
391    /// #    mut output_buffer: odbc_api::handles::OutputStringBuffer,
392    /// #    env: odbc_api::Environment,
393    /// # ) -> Result<(), odbc_api::Error> {
394    /// let already_sufficient = "DSN=MicrosoftAccessFile;";
395    /// let connection = env.driver_connect(
396    ///    &already_sufficient,
397    ///    &mut output_buffer,
398    ///    DriverCompleteOption::NoPrompt,
399    /// )?;
400    /// let connection_string = output_buffer.to_utf8();
401    ///
402    /// // Now `connection_string` might be something like
403    /// // `DSN=MicrosoftAccessFile;DBQ=C:\Db\Example.accdb;DriverId=25;FIL=MS Access;MaxBufferSize=2048;`
404    /// # Ok(()) }
405    /// ```
406    pub fn driver_connect(
407        &self,
408        connection_string: &str,
409        completed_connection_string: &mut OutputStringBuffer,
410        driver_completion: DriverCompleteOption,
411    ) -> Result<Connection<'_>, Error> {
412        let mut driver_connect = |hwnd: HWnd| unsafe {
413            self.driver_connect_with_hwnd(
414                connection_string,
415                completed_connection_string,
416                driver_completion,
417                hwnd,
418            )
419        };
420
421        match driver_completion {
422            DriverCompleteOption::NoPrompt => (),
423            #[cfg(all(target_os = "windows", feature = "prompt"))]
424            _ => {
425                // We need a parent window, let's provide a message only window.
426                let mut window_app = MessageOnlyWindowEventHandler {
427                    run_prompt_dialog: Some(driver_connect),
428                    result: None,
429                };
430                let mut event_loop = EventLoop::new().unwrap();
431                event_loop.run_app_on_demand(&mut window_app).unwrap();
432                return window_app.result.unwrap();
433            }
434        };
435        let hwnd = null_mut();
436        driver_connect(hwnd)
437    }
438
439    /// Allows to call driver connect with a user supplied HWnd. Same as [`Self::driver_connect`],
440    /// but with the possibility to provide your own parent window handle in case you want to show
441    /// a prompt to the user.
442    ///
443    /// # Safety
444    ///
445    /// `parent_window` must be a valid window handle, to a window type supported by the ODBC driver
446    /// manager. On windows this is a plain window handle, which is of course understood by the
447    /// windows built in ODBC driver manager. Other working combinations are unknown to the author.
448    pub unsafe fn driver_connect_with_hwnd(
449        &self,
450        connection_string: &str,
451        completed_connection_string: &mut OutputStringBuffer,
452        driver_completion: DriverCompleteOption,
453        parent_window: HWnd,
454    ) -> Result<Connection<'_>, Error> {
455        let mut connection = self.allocate_connection()?;
456        let connection_string = SqlText::new(connection_string);
457
458        let connection_string_is_complete = unsafe {
459            connection.driver_connect(
460                &connection_string,
461                parent_window,
462                completed_connection_string,
463                driver_completion.as_sys(),
464            )
465        }
466        .into_result_bool(&connection)?;
467        if !connection_string_is_complete {
468            return Err(Error::AbortedConnectionStringCompletion);
469        }
470        Ok(Connection::new(connection))
471    }
472
473    /// Get information about available drivers. Only 32 or 64 Bit drivers will be listed, depending
474    /// on whether you are building a 32 Bit or 64 Bit application.
475    ///
476    /// # Example
477    ///
478    /// ```no_run
479    /// use odbc_api::Environment;
480    ///
481    /// let env = Environment::new ()?;
482    /// for driver_info in env.drivers()? {
483    ///     println!("{:#?}", driver_info);
484    /// }
485    ///
486    /// # Ok::<_, odbc_api::Error>(())
487    /// ```
488    pub fn drivers(&self) -> Result<Vec<DriverInfo>, Error> {
489        let mut driver_info = Vec::new();
490
491        // Since we have exclusive ownership of the environment handle and we take the lock, we can
492        // guarantee that this method is currently the only one changing the state of the internal
493        // iterators of the environment.
494        let _lock = self.internal_state.lock().unwrap();
495        unsafe {
496            // Find required buffer size to avoid truncation.
497            let (mut desc_len, mut attr_len) = if let Some(res) = self
498                .environment
499                // Start with first so we are independent of state
500                .drivers_buffer_len(FetchOrientation::First)
501                .map(Some)
502                .on_no_data(|| None)
503                .into_result(&self.environment)?
504            {
505                res
506            } else {
507                // No drivers present
508                return Ok(Vec::new());
509            };
510
511            // If there are, let's loop over the remaining drivers
512            while let Some((candidate_desc_len, candidate_attr_len)) = self
513                .environment
514                .drivers_buffer_len(FetchOrientation::Next)
515                .or_no_data()
516                .into_result(&self.environment)?
517            {
518                desc_len = max(candidate_desc_len, desc_len);
519                attr_len = max(candidate_attr_len, attr_len);
520            }
521
522            // Allocate +1 character extra for terminating zero
523            let mut desc_buf = SzBuffer::with_capacity(desc_len as usize);
524            // Do **not** use nul terminated buffer, as nul is used to delimit key value pairs of
525            // attributes.
526            let mut attr_buf: Vec<SqlChar> = vec![0; attr_len as usize];
527
528            while self
529                .environment
530                .drivers_buffer_fill(FetchOrientation::Next, desc_buf.mut_buf(), &mut attr_buf)
531                .into_result_bool(&self.environment)?
532            {
533                let description = desc_buf.to_utf8();
534                let attributes =
535                    slice_to_utf8(&attr_buf).expect("Attributes must be interpretable as UTF-8");
536
537                let attributes = attributes_iter(&attributes).collect();
538
539                driver_info.push(DriverInfo {
540                    description,
541                    attributes,
542                });
543            }
544        }
545
546        Ok(driver_info)
547    }
548
549    /// User and system data sources
550    ///
551    /// # Example
552    ///
553    /// ```no_run
554    /// use odbc_api::Environment;
555    ///
556    /// let env = Environment::new()?;
557    /// for data_source in env.data_sources()? {
558    ///     println!("{:#?}", data_source);
559    /// }
560    ///
561    /// # Ok::<_, odbc_api::Error>(())
562    /// ```
563    pub fn data_sources(&self) -> Result<Vec<DataSourceInfo>, Error> {
564        self.data_sources_impl(FetchOrientation::First)
565    }
566
567    /// Only system data sources
568    ///
569    /// # Example
570    ///
571    /// ```no_run
572    /// use odbc_api::Environment;
573    ///
574    /// let env = Environment::new ()?;
575    /// for data_source in env.system_data_sources()? {
576    ///     println!("{:#?}", data_source);
577    /// }
578    ///
579    /// # Ok::<_, odbc_api::Error>(())
580    /// ```
581    pub fn system_data_sources(&self) -> Result<Vec<DataSourceInfo>, Error> {
582        self.data_sources_impl(FetchOrientation::FirstSystem)
583    }
584
585    /// Only user data sources
586    ///
587    /// # Example
588    ///
589    /// ```no_run
590    /// use odbc_api::Environment;
591    ///
592    /// let mut env = unsafe { Environment::new () }?;
593    /// for data_source in env.user_data_sources()? {
594    ///     println!("{:#?}", data_source);
595    /// }
596    ///
597    /// # Ok::<_, odbc_api::Error>(())
598    /// ```
599    pub fn user_data_sources(&self) -> Result<Vec<DataSourceInfo>, Error> {
600        self.data_sources_impl(FetchOrientation::FirstUser)
601    }
602
603    fn data_sources_impl(&self, direction: FetchOrientation) -> Result<Vec<DataSourceInfo>, Error> {
604        let mut data_source_info = Vec::new();
605
606        // Since we have exclusive ownership of the environment handle and we take the lock, we can
607        // guarantee that this method is currently the only one changing the state of the internal
608        // iterators of the environment.
609        let _lock = self.internal_state.lock().unwrap();
610        unsafe {
611            // Find required buffer size to avoid truncation.
612            let (mut server_name_len, mut driver_len) = if let Some(res) = self
613                .environment
614                .data_source_buffer_len(direction)
615                .or_no_data()
616                .into_result(&self.environment)?
617            {
618                res
619            } else {
620                // No drivers present
621                return Ok(Vec::new());
622            };
623
624            // If there are let's loop over the rest
625            while let Some((candidate_name_len, candidate_decs_len)) = self
626                .environment
627                .drivers_buffer_len(FetchOrientation::Next)
628                .or_no_data()
629                .into_result(&self.environment)?
630            {
631                server_name_len = max(candidate_name_len, server_name_len);
632                driver_len = max(candidate_decs_len, driver_len);
633            }
634
635            let mut server_name_buf = SzBuffer::with_capacity(server_name_len as usize);
636            let mut driver_buf = SzBuffer::with_capacity(driver_len as usize);
637
638            let mut not_empty = self
639                .environment
640                .data_source_buffer_fill(direction, server_name_buf.mut_buf(), driver_buf.mut_buf())
641                .into_result_bool(&self.environment)?;
642
643            while not_empty {
644                let server_name = server_name_buf.to_utf8();
645                let driver = driver_buf.to_utf8();
646
647                data_source_info.push(DataSourceInfo {
648                    server_name,
649                    driver,
650                });
651                not_empty = self
652                    .environment
653                    .data_source_buffer_fill(
654                        FetchOrientation::Next,
655                        server_name_buf.mut_buf(),
656                        driver_buf.mut_buf(),
657                    )
658                    .into_result_bool(&self.environment)?;
659            }
660        }
661
662        Ok(data_source_info)
663    }
664
665    fn allocate_connection(&self) -> Result<handles::Connection<'_>, Error> {
666        // Hold lock diagnostics errors are consumed in this thread.
667        let _lock = self.internal_state.lock().unwrap();
668        self.environment
669            .allocate_connection()
670            .into_result(&self.environment)
671    }
672}
673
674/// An ODBC [`Environment`] with static lifetime. This function always returns a reference to the
675/// same instance. The environment is constructed then the function is called for the first time.
676/// Every time after the initial construction this function must succeed.
677///
678/// Useful if your application uses ODBC for the entirety of its lifetime, since using a static
679/// lifetime means there is one less lifetime you and the borrow checker need to worry about. If
680/// your application only wants to use odbc for part of its runtime, you may want to use
681/// [`Environment`] directly in order to explicitly free its associated resources earlier. No matter
682/// the application, it is recommended to only have one [`Environment`] per process.
683pub fn environment() -> Result<&'static Environment, Error> {
684    static ENV: OnceLock<Environment> = OnceLock::new();
685    if let Some(env) = ENV.get() {
686        // Environment already initialized, nothing to do, but to return it.
687        Ok(env)
688    } else {
689        // ODBC Environment not initialized yet. Let's do so and return it.
690        let env = Environment::new()?;
691        let env = ENV.get_or_init(|| env);
692        Ok(env)
693    }
694}
695
696/// Struct holding information available on a driver. Can be obtained via [`Environment::drivers`].
697#[derive(Clone, Debug, Eq, PartialEq)]
698pub struct DriverInfo {
699    /// Name of the ODBC driver
700    pub description: String,
701    /// Attributes values of the driver by key
702    pub attributes: HashMap<String, String>,
703}
704
705/// Holds name and description of a datasource
706///
707/// Can be obtained via [`Environment::data_sources`]
708#[derive(Clone, Debug, Eq, PartialEq)]
709pub struct DataSourceInfo {
710    /// Name of the data source
711    pub server_name: String,
712    /// Description of the data source
713    pub driver: String,
714}
715
716/// Message loop for prompt dialog. Used by [`Environment::driver_connect`].
717#[cfg(all(target_os = "windows", feature = "prompt"))]
718struct MessageOnlyWindowEventHandler<'a, F> {
719    run_prompt_dialog: Option<F>,
720    result: Option<Result<Connection<'a>, Error>>,
721}
722
723#[cfg(all(target_os = "windows", feature = "prompt"))]
724impl<'a, F> ApplicationHandler for MessageOnlyWindowEventHandler<'a, F>
725where
726    F: FnOnce(HWnd) -> Result<Connection<'a>, Error>,
727{
728    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
729        let parent_window = event_loop
730            .create_window(Window::default_attributes().with_visible(false))
731            .unwrap();
732
733        use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle, Win32WindowHandle};
734
735        let hwnd = match parent_window.window_handle().unwrap().as_raw() {
736            RawWindowHandle::Win32(Win32WindowHandle { hwnd, .. }) => hwnd.get() as HWnd,
737            _ => panic!("ODBC Prompt is only supported on window platforms"),
738        };
739
740        if let Some(run_dialog) = self.run_prompt_dialog.take() {
741            self.result = Some(run_dialog(hwnd))
742        }
743        event_loop.exit();
744    }
745
746    fn window_event(&mut self, _event_loop: &ActiveEventLoop, _id: WindowId, _event: WindowEvent) {}
747}
748
749/// Called by drivers to pares list of attributes
750///
751/// Key value pairs are separated by `\0`. Key and value are separated by `=`
752fn attributes_iter(attributes: &str) -> impl Iterator<Item = (String, String)> + '_ {
753    attributes
754        .split('\0')
755        .take_while(|kv_str| *kv_str != String::new())
756        .map(|kv_str| {
757            let mut iter = kv_str.split('=');
758            let key = iter.next().unwrap();
759            let value = iter.next().unwrap();
760            (key.to_string(), value.to_string())
761        })
762}
763
764#[cfg(test)]
765mod tests {
766
767    use super::*;
768
769    #[test]
770    fn parse_attributes() {
771        let buffer = "APILevel=2\0ConnectFunctions=YYY\0CPTimeout=60\0DriverODBCVer=03.\
772                      50\0FileUsage=0\0SQLLevel=1\0UsageCount=1\0\0";
773        let attributes: HashMap<_, _> = attributes_iter(buffer).collect();
774        assert_eq!(attributes["APILevel"], "2");
775        assert_eq!(attributes["ConnectFunctions"], "YYY");
776        assert_eq!(attributes["CPTimeout"], "60");
777        assert_eq!(attributes["DriverODBCVer"], "03.50");
778        assert_eq!(attributes["FileUsage"], "0");
779        assert_eq!(attributes["SQLLevel"], "1");
780        assert_eq!(attributes["UsageCount"], "1");
781    }
782}