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