odbc_api/
connection.rs

1use crate::{
2    buffers::BufferDesc,
3    execute::{
4        execute_columns, execute_foreign_keys, execute_tables, execute_with_parameters,
5        execute_with_parameters_polling,
6    },
7    handles::{self, slice_to_utf8, SqlText, State, Statement, StatementImpl},
8    statement_connection::StatementConnection,
9    CursorImpl, CursorPolling, Error, ParameterCollectionRef, Preallocated, Prepared, Sleep,
10};
11use log::error;
12use odbc_sys::HDbc;
13use std::{
14    borrow::Cow,
15    fmt::{self, Debug, Display},
16    mem::ManuallyDrop,
17    str,
18    thread::panicking,
19};
20
21impl Drop for Connection<'_> {
22    fn drop(&mut self) {
23        match self.connection.disconnect().into_result(&self.connection) {
24            Ok(()) => (),
25            Err(Error::Diagnostics {
26                record,
27                function: _,
28            }) if record.state == State::INVALID_STATE_TRANSACTION => {
29                // Invalid transaction state. Let's rollback the current transaction and try again.
30                if let Err(e) = self.rollback() {
31                    // Connection might be in a suspended state. See documentation about suspended
32                    // state here:
33                    // <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlendtran-function>
34                    //
35                    // See also issue:
36                    // <https://github.com/pacman82/odbc-api/issues/574#issuecomment-2286449125>
37
38                    error!(
39                        "Error during rolling back transaction (In order to recover from \
40                        invalid transaction state during disconnect {}",
41                        e
42                    );
43                }
44                // Transaction might be rolled back or suspended. Now let's try again to disconnect.
45                if let Err(e) = self.connection.disconnect().into_result(&self.connection) {
46                    // Avoid panicking, if we already have a panic. We don't want to mask the
47                    // original error.
48                    if !panicking() {
49                        panic!("Unexpected error disconnecting (after rollback attempt): {e:?}")
50                    }
51                }
52            }
53            Err(e) => {
54                // Avoid panicking, if we already have a panic. We don't want to mask the original
55                // error.
56                if !panicking() {
57                    panic!("Unexpected error disconnecting: {e:?}")
58                }
59            }
60        }
61    }
62}
63
64/// The connection handle references storage of all information about the connection to the data
65/// source, including status, transaction state, and error information.
66///
67/// If you want to enable the connection pooling support build into the ODBC driver manager have a
68/// look at [`crate::Environment::set_connection_pooling`].
69pub struct Connection<'c> {
70    connection: handles::Connection<'c>,
71}
72
73impl<'c> Connection<'c> {
74    pub(crate) fn new(connection: handles::Connection<'c>) -> Self {
75        Self { connection }
76    }
77
78    /// Transfers ownership of the handle to this open connection to the raw ODBC pointer.
79    pub fn into_sys(self) -> HDbc {
80        // We do not want to run the drop handler, but transfer ownership instead.
81        ManuallyDrop::new(self).connection.as_sys()
82    }
83
84    /// Transfer ownership of this open connection to a wrapper around the raw ODBC pointer. The
85    /// wrapper allows you to call ODBC functions on the handle, but doesn't care if the connection
86    /// is in the right state.
87    ///
88    /// You should not have a need to call this method if your use case is covered by this library,
89    /// but, in case it is not, this may help you to break out of the type structure which might be
90    /// to rigid for you, while simultaneously abondoning its safeguards.
91    pub fn into_handle(self) -> handles::Connection<'c> {
92        unsafe { handles::Connection::new(ManuallyDrop::new(self).connection.as_sys()) }
93    }
94
95    /// Executes an SQL statement. This is the fastest way to submit an SQL statement for one-time
96    /// execution.
97    ///
98    /// # Parameters
99    ///
100    /// * `query`: The text representation of the SQL statement. E.g. "SELECT * FROM my_table;".
101    /// * `params`: `?` may be used as a placeholder in the statement text. You can use `()` to
102    ///   represent no parameters. See the [`crate::parameter`] module level documentation for more
103    ///   information on how to pass parameters.
104    ///
105    /// # Return
106    ///
107    /// Returns `Some` if a cursor is created. If `None` is returned no cursor has been created (
108    /// e.g. the query came back empty). Note that an empty query may also create a cursor with zero
109    /// rows.
110    ///
111    /// # Example
112    ///
113    /// ```no_run
114    /// use odbc_api::{Environment, ConnectionOptions};
115    ///
116    /// let env = Environment::new()?;
117    ///
118    /// let mut conn = env.connect(
119    ///     "YourDatabase", "SA", "My@Test@Password1",
120    ///     ConnectionOptions::default()
121    /// )?;
122    /// if let Some(cursor) = conn.execute("SELECT year, name FROM Birthdays;", ())? {
123    ///     // Use cursor to process query results.  
124    /// }
125    /// # Ok::<(), odbc_api::Error>(())
126    /// ```
127    pub fn execute(
128        &self,
129        query: &str,
130        params: impl ParameterCollectionRef,
131    ) -> Result<Option<CursorImpl<StatementImpl<'_>>>, Error> {
132        let query = SqlText::new(query);
133        let lazy_statement = move || self.allocate_statement();
134        execute_with_parameters(lazy_statement, Some(&query), params)
135    }
136
137    /// Asynchronous sibling of [`Self::execute`]. Uses polling mode to be asynchronous. `sleep`
138    /// does govern the behaviour of polling, by waiting for the future in between polling. Sleep
139    /// should not be implemented using a sleep which blocks the system thread, but rather utilize
140    /// the methods provided by your async runtime. E.g.:
141    ///
142    /// ```
143    /// use odbc_api::{Connection, IntoParameter, Error};
144    /// use std::time::Duration;
145    ///
146    /// async fn insert_post<'a>(
147    ///     connection: &'a Connection<'a>,
148    ///     user: &str,
149    ///     post: &str,
150    /// ) -> Result<(), Error> {
151    ///     // Poll every 50 ms.
152    ///     let sleep = || tokio::time::sleep(Duration::from_millis(50));
153    ///     let sql = "INSERT INTO POSTS (user, post) VALUES (?, ?)";
154    ///     // Execute query using ODBC polling method
155    ///     let params = (&user.into_parameter(), &post.into_parameter());
156    ///     connection.execute_polling(&sql, params, sleep).await?;
157    ///     Ok(())
158    /// }
159    /// ```
160    ///
161    /// **Attention**: This feature requires driver support, otherwise the calls will just block
162    /// until they are finished. At the time of writing this out of Microsoft SQL Server,
163    /// PostgerSQL, SQLite and MariaDB this worked only with Microsoft SQL Server. For code generic
164    /// over every driver you may still use this. The functions will return with the correct results
165    /// just be aware that may block until they are finished.
166    pub async fn execute_polling(
167        &self,
168        query: &str,
169        params: impl ParameterCollectionRef,
170        sleep: impl Sleep,
171    ) -> Result<Option<CursorPolling<StatementImpl<'_>>>, Error> {
172        let query = SqlText::new(query);
173        let lazy_statement = move || {
174            let mut stmt = self.allocate_statement()?;
175            stmt.set_async_enable(true).into_result(&stmt)?;
176            Ok(stmt)
177        };
178        execute_with_parameters_polling(lazy_statement, Some(&query), params, sleep).await
179    }
180
181    /// In some use cases there you only execute a single statement, or the time to open a
182    /// connection does not matter users may wish to choose to not keep a connection alive seperatly
183    /// from the cursor, in order to have an easier time with the borrow checker.
184    ///
185    /// ```no_run
186    /// use odbc_api::{environment, Error, Cursor, ConnectionOptions};
187    ///
188    ///
189    /// const CONNECTION_STRING: &str =
190    ///     "Driver={ODBC Driver 18 for SQL Server};\
191    ///     Server=localhost;UID=SA;\
192    ///     PWD=My@Test@Password1;";
193    ///
194    /// fn execute_query(query: &str) -> Result<Option<impl Cursor>, Error> {
195    ///     let env = environment()?;
196    ///     let conn = env.connect_with_connection_string(
197    ///         CONNECTION_STRING,
198    ///         ConnectionOptions::default()
199    ///     )?;
200    ///
201    ///     // connect.execute(&query, ()) // Compiler error: Would return local ref to `conn`.
202    ///
203    ///     let maybe_cursor = conn.into_cursor(&query, ())?;
204    ///     Ok(maybe_cursor)
205    /// }
206    /// ```
207    pub fn into_cursor(
208        self,
209        query: &str,
210        params: impl ParameterCollectionRef,
211    ) -> Result<Option<CursorImpl<StatementConnection<'c>>>, ConnectionAndError<'c>> {
212        // With the current Rust version the borrow checker needs some convincing, so that it allows
213        // us to return the Connection, even though the Result of execute borrows it.
214        let mut error = None;
215        let mut cursor = None;
216        match self.execute(query, params) {
217            Ok(Some(c)) => cursor = Some(c),
218            Ok(None) => return Ok(None),
219            Err(e) => error = Some(e),
220        };
221        if let Some(e) = error {
222            drop(cursor);
223            return Err(ConnectionAndError {
224                error: e,
225                connection: self,
226            });
227        }
228        let cursor = cursor.unwrap();
229        // The rust compiler needs some help here. It assumes otherwise that the lifetime of the
230        // resulting cursor would depend on the lifetime of `params`.
231        let mut cursor = ManuallyDrop::new(cursor);
232        let handle = cursor.as_sys();
233        // Safe: `handle` is a valid statement, and we are giving up ownership of `self`.
234        let statement = unsafe { StatementConnection::new(handle, self) };
235        // Safe: `statement is in the cursor state`.
236        let cursor = unsafe { CursorImpl::new(statement) };
237        Ok(Some(cursor))
238    }
239
240    /// Prepares an SQL statement. This is recommended for repeated execution of similar queries.
241    ///
242    /// Should your use case require you to execute the same query several times with different
243    /// parameters, prepared queries are the way to go. These give the database a chance to cache
244    /// the access plan associated with your SQL statement. It is not unlike compiling your program
245    /// once and executing it several times.
246    ///
247    /// ```
248    /// use odbc_api::{Connection, Error, IntoParameter};
249    /// use std::io::{self, stdin, Read};
250    ///
251    /// fn interactive(conn: &Connection) -> io::Result<()>{
252    ///     let mut prepared = conn.prepare("SELECT * FROM Movies WHERE title=?;").unwrap();
253    ///     let mut title = String::new();
254    ///     stdin().read_line(&mut title)?;
255    ///     while !title.is_empty() {
256    ///         match prepared.execute(&title.as_str().into_parameter()) {
257    ///             Err(e) => println!("{}", e),
258    ///             // Most drivers would return a result set even if no Movie with the title is found,
259    ///             // the result set would just be empty. Well, most drivers.
260    ///             Ok(None) => println!("No result set generated."),
261    ///             Ok(Some(cursor)) => {
262    ///                 // ...print cursor contents...
263    ///             }
264    ///         }
265    ///         stdin().read_line(&mut title)?;
266    ///     }
267    ///     Ok(())
268    /// }
269    /// ```
270    ///
271    /// # Parameters
272    ///
273    /// * `query`: The text representation of the SQL statement. E.g. "SELECT * FROM my_table;". `?`
274    ///   may be used as a placeholder in the statement text, to be replaced with parameters during
275    ///   execution.
276    pub fn prepare(&self, query: &str) -> Result<Prepared<StatementImpl<'_>>, Error> {
277        let query = SqlText::new(query);
278        let mut stmt = self.allocate_statement()?;
279        stmt.prepare(&query).into_result(&stmt)?;
280        Ok(Prepared::new(stmt))
281    }
282
283    /// Prepares an SQL statement which takes ownership of the connection. The advantage over
284    /// [`Self::prepare`] is, that you do not need to keep track of the lifetime of the connection
285    /// seperatly and can create types which do own the prepared query and only depend on the
286    /// lifetime of the environment. The downside is that you can not use the connection for
287    /// anything else anymore.
288    ///
289    /// # Parameters
290    ///
291    /// * `query`: The text representation of the SQL statement. E.g. "SELECT * FROM my_table;". `?`
292    ///   may be used as a placeholder in the statement text, to be replaced with parameters during
293    ///   execution.
294    ///
295    /// ```no_run
296    /// use odbc_api::{
297    ///     environment, Error, ColumnarBulkInserter, StatementConnection,
298    ///     buffers::{BufferDesc, AnyBuffer}, ConnectionOptions
299    /// };
300    ///
301    /// const CONNECTION_STRING: &str =
302    ///     "Driver={ODBC Driver 18 for SQL Server};\
303    ///     Server=localhost;UID=SA;\
304    ///     PWD=My@Test@Password1;";
305    ///
306    /// /// Supports columnar bulk inserts on a heterogenous schema (columns have different types),
307    /// /// takes ownership of a connection created using an environment with static lifetime.
308    /// type Inserter = ColumnarBulkInserter<StatementConnection<'static>, AnyBuffer>;
309    ///
310    /// /// Creates an inserter which can be reused to bulk insert birthyears with static lifetime.
311    /// fn make_inserter(query: &str) -> Result<Inserter, Error> {
312    ///     let env = environment()?;
313    ///     let conn = env.connect_with_connection_string(
314    ///         CONNECTION_STRING,
315    ///         ConnectionOptions::default()
316    ///     )?;
317    ///     let prepared = conn.into_prepared("INSERT INTO Birthyear (name, year) VALUES (?, ?)")?;
318    ///     let buffers = [
319    ///         BufferDesc::Text { max_str_len: 255},
320    ///         BufferDesc::I16 { nullable: false },
321    ///     ];
322    ///     let capacity = 400;
323    ///     prepared.into_column_inserter(capacity, buffers)
324    /// }
325    /// ```
326    pub fn into_prepared(self, query: &str) -> Result<Prepared<StatementConnection<'c>>, Error> {
327        let query = SqlText::new(query);
328        let mut stmt = self.allocate_statement()?;
329        stmt.prepare(&query).into_result(&stmt)?;
330        // Safe: `handle` is a valid statement, and we are giving up ownership of `self`.
331        let stmt = unsafe { StatementConnection::new(stmt.into_sys(), self) };
332        Ok(Prepared::new(stmt))
333    }
334
335    /// Allocates an SQL statement handle. This is recommended if you want to sequentially execute
336    /// different queries over the same connection, as you avoid the overhead of allocating a
337    /// statement handle for each query.
338    ///
339    /// Should you want to repeatedly execute the same query with different parameters try
340    /// [`Self::prepare`] instead.
341    ///
342    /// # Example
343    ///
344    /// ```
345    /// use odbc_api::{Connection, Error};
346    /// use std::io::{self, stdin, Read};
347    ///
348    /// fn interactive(conn: &Connection) -> io::Result<()>{
349    ///     let mut statement = conn.preallocate().unwrap();
350    ///     let mut query = String::new();
351    ///     stdin().read_line(&mut query)?;
352    ///     while !query.is_empty() {
353    ///         match statement.execute(&query, ()) {
354    ///             Err(e) => println!("{}", e),
355    ///             Ok(None) => println!("No results set generated."),
356    ///             Ok(Some(cursor)) => {
357    ///                 // ...print cursor contents...
358    ///             },
359    ///         }
360    ///         stdin().read_line(&mut query)?;
361    ///     }
362    ///     Ok(())
363    /// }
364    /// ```
365    pub fn preallocate(&self) -> Result<Preallocated<'_>, Error> {
366        let stmt = self.allocate_statement()?;
367        unsafe { Ok(Preallocated::new(stmt)) }
368    }
369
370    /// Specify the transaction mode. By default, ODBC transactions are in auto-commit mode.
371    /// Switching from manual-commit mode to auto-commit mode automatically commits any open
372    /// transaction on the connection. There is no open or begin transaction method. Each statement
373    /// execution automatically starts a new transaction or adds to the existing one.
374    ///
375    /// In manual commit mode you can use [`Connection::commit`] or [`Connection::rollback`]. Keep
376    /// in mind, that even `SELECT` statements can open new transactions. This library will rollback
377    /// open transactions if a connection goes out of SCOPE. This however will log an error, since
378    /// the transaction state is only discovered during a failed disconnect. It is preferable that
379    /// the application makes sure all transactions are closed if in manual commit mode.
380    pub fn set_autocommit(&self, enabled: bool) -> Result<(), Error> {
381        self.connection
382            .set_autocommit(enabled)
383            .into_result(&self.connection)
384    }
385
386    /// To commit a transaction in manual-commit mode.
387    pub fn commit(&self) -> Result<(), Error> {
388        self.connection.commit().into_result(&self.connection)
389    }
390
391    /// To rollback a transaction in manual-commit mode.
392    pub fn rollback(&self) -> Result<(), Error> {
393        self.connection.rollback().into_result(&self.connection)
394    }
395
396    /// Indicates the state of the connection. If `true` the connection has been lost. If `false`,
397    /// the connection is still active.
398    pub fn is_dead(&self) -> Result<bool, Error> {
399        self.connection.is_dead().into_result(&self.connection)
400    }
401
402    /// Network packet size in bytes. Requries driver support.
403    pub fn packet_size(&self) -> Result<u32, Error> {
404        self.connection.packet_size().into_result(&self.connection)
405    }
406
407    /// Get the name of the database management system used by the connection.
408    pub fn database_management_system_name(&self) -> Result<String, Error> {
409        let mut buf = Vec::new();
410        self.connection
411            .fetch_database_management_system_name(&mut buf)
412            .into_result(&self.connection)?;
413        let name = slice_to_utf8(&buf).unwrap();
414        Ok(name)
415    }
416
417    /// Maximum length of catalog names.
418    pub fn max_catalog_name_len(&self) -> Result<u16, Error> {
419        self.connection
420            .max_catalog_name_len()
421            .into_result(&self.connection)
422    }
423
424    /// Maximum length of schema names.
425    pub fn max_schema_name_len(&self) -> Result<u16, Error> {
426        self.connection
427            .max_schema_name_len()
428            .into_result(&self.connection)
429    }
430
431    /// Maximum length of table names.
432    pub fn max_table_name_len(&self) -> Result<u16, Error> {
433        self.connection
434            .max_table_name_len()
435            .into_result(&self.connection)
436    }
437
438    /// Maximum length of column names.
439    pub fn max_column_name_len(&self) -> Result<u16, Error> {
440        self.connection
441            .max_column_name_len()
442            .into_result(&self.connection)
443    }
444
445    /// Get the name of the current catalog being used by the connection.
446    pub fn current_catalog(&self) -> Result<String, Error> {
447        let mut buf = Vec::new();
448        self.connection
449            .fetch_current_catalog(&mut buf)
450            .into_result(&self.connection)?;
451        let name = slice_to_utf8(&buf).expect("Return catalog must be correctly encoded");
452        Ok(name)
453    }
454
455    /// A cursor describing columns of all tables matching the patterns. Patterns support as
456    /// placeholder `%` for multiple characters or `_` for a single character. Use `\` to escape.The
457    /// returned cursor has the columns:
458    /// `TABLE_CAT`, `TABLE_SCHEM`, `TABLE_NAME`, `COLUMN_NAME`, `DATA_TYPE`, `TYPE_NAME`,
459    /// `COLUMN_SIZE`, `BUFFER_LENGTH`, `DECIMAL_DIGITS`, `NUM_PREC_RADIX`, `NULLABLE`,
460    /// `REMARKS`, `COLUMN_DEF`, `SQL_DATA_TYPE`, `SQL_DATETIME_SUB`, `CHAR_OCTET_LENGTH`,
461    /// `ORDINAL_POSITION`, `IS_NULLABLE`.
462    ///
463    /// In addition to that there may be a number of columns specific to the data source.
464    pub fn columns(
465        &self,
466        catalog_name: &str,
467        schema_name: &str,
468        table_name: &str,
469        column_name: &str,
470    ) -> Result<CursorImpl<StatementImpl<'_>>, Error> {
471        execute_columns(
472            self.allocate_statement()?,
473            &SqlText::new(catalog_name),
474            &SqlText::new(schema_name),
475            &SqlText::new(table_name),
476            &SqlText::new(column_name),
477        )
478    }
479
480    /// List tables, schemas, views and catalogs of a datasource.
481    ///
482    /// # Parameters
483    ///
484    /// * `catalog_name`: Filter result by catalog name. Accept search patterns. Use `%` to match
485    ///   any number of characters. Use `_` to match exactly on character. Use `\` to escape
486    ///   characeters.
487    /// * `schema_name`: Filter result by schema. Accepts patterns in the same way as
488    ///   `catalog_name`.
489    /// * `table_name`: Filter result by table. Accepts patterns in the same way as `catalog_name`.
490    /// * `table_type`: Filters results by table type. E.g: 'TABLE', 'VIEW'. This argument accepts a
491    ///   comma separeted list of table types. Omit it to not filter the result by table type at
492    ///   all.
493    ///
494    /// # Example
495    ///
496    /// ```
497    /// use odbc_api::{Connection, Cursor, Error, ResultSetMetadata, buffers::TextRowSet};
498    ///
499    /// fn print_all_tables(conn: &Connection<'_>) -> Result<(), Error> {
500    ///     // Set all filters to an empty string, to really print all tables
501    ///     let mut cursor = conn.tables("", "", "", "")?;
502    ///
503    ///     // The column are gonna be TABLE_CAT,TABLE_SCHEM,TABLE_NAME,TABLE_TYPE,REMARKS, but may
504    ///     // also contain additional driver specific columns.
505    ///     for (index, name) in cursor.column_names()?.enumerate() {
506    ///         if index != 0 {
507    ///             print!(",")
508    ///         }
509    ///         print!("{}", name?);
510    ///     }
511    ///
512    ///     let batch_size = 100;
513    ///     let mut buffer = TextRowSet::for_cursor(batch_size, &mut cursor, Some(4096))?;
514    ///     let mut row_set_cursor = cursor.bind_buffer(&mut buffer)?;
515    ///
516    ///     while let Some(row_set) = row_set_cursor.fetch()? {
517    ///         for row_index in 0..row_set.num_rows() {
518    ///             if row_index != 0 {
519    ///                 print!("\n");
520    ///             }
521    ///             for col_index in 0..row_set.num_cols() {
522    ///                 if col_index != 0 {
523    ///                     print!(",");
524    ///                 }
525    ///                 let value = row_set
526    ///                     .at_as_str(col_index, row_index)
527    ///                     .unwrap()
528    ///                     .unwrap_or("NULL");
529    ///                 print!("{}", value);
530    ///             }
531    ///         }
532    ///     }
533    ///
534    ///     Ok(())
535    /// }
536    /// ```
537    pub fn tables(
538        &self,
539        catalog_name: &str,
540        schema_name: &str,
541        table_name: &str,
542        table_type: &str,
543    ) -> Result<CursorImpl<StatementImpl<'_>>, Error> {
544        let statement = self.allocate_statement()?;
545
546        execute_tables(
547            statement,
548            &SqlText::new(catalog_name),
549            &SqlText::new(schema_name),
550            &SqlText::new(table_name),
551            &SqlText::new(table_type),
552        )
553    }
554
555    /// This can be used to retrieve either a list of foreign keys in the specified table or a list
556    /// of foreign keys in other table that refer to the primary key of the specified table.
557    ///
558    /// See: <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlforeignkeys-function>
559    pub fn foreign_keys(
560        &self,
561        pk_catalog_name: &str,
562        pk_schema_name: &str,
563        pk_table_name: &str,
564        fk_catalog_name: &str,
565        fk_schema_name: &str,
566        fk_table_name: &str,
567    ) -> Result<CursorImpl<StatementImpl<'_>>, Error> {
568        let statement = self.allocate_statement()?;
569
570        execute_foreign_keys(
571            statement,
572            &SqlText::new(pk_catalog_name),
573            &SqlText::new(pk_schema_name),
574            &SqlText::new(pk_table_name),
575            &SqlText::new(fk_catalog_name),
576            &SqlText::new(fk_schema_name),
577            &SqlText::new(fk_table_name),
578        )
579    }
580
581    /// The buffer descriptions for all standard buffers (not including extensions) returned in the
582    /// columns query (e.g. [`Connection::columns`]).
583    ///
584    /// # Arguments
585    ///
586    /// * `type_name_max_len` - The maximum expected length of type names.
587    /// * `remarks_max_len` - The maximum expected length of remarks.
588    /// * `column_default_max_len` - The maximum expected length of column defaults.
589    pub fn columns_buffer_descs(
590        &self,
591        type_name_max_len: usize,
592        remarks_max_len: usize,
593        column_default_max_len: usize,
594    ) -> Result<Vec<BufferDesc>, Error> {
595        let null_i16 = BufferDesc::I16 { nullable: true };
596
597        let not_null_i16 = BufferDesc::I16 { nullable: false };
598
599        let null_i32 = BufferDesc::I32 { nullable: true };
600
601        // The definitions for these descriptions are taken from the documentation of `SQLColumns`
602        // located at https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlcolumns-function
603        let catalog_name_desc = BufferDesc::Text {
604            max_str_len: self.max_catalog_name_len()? as usize,
605        };
606
607        let schema_name_desc = BufferDesc::Text {
608            max_str_len: self.max_schema_name_len()? as usize,
609        };
610
611        let table_name_desc = BufferDesc::Text {
612            max_str_len: self.max_table_name_len()? as usize,
613        };
614
615        let column_name_desc = BufferDesc::Text {
616            max_str_len: self.max_column_name_len()? as usize,
617        };
618
619        let data_type_desc = not_null_i16;
620
621        let type_name_desc = BufferDesc::Text {
622            max_str_len: type_name_max_len,
623        };
624
625        let column_size_desc = null_i32;
626        let buffer_len_desc = null_i32;
627        let decimal_digits_desc = null_i16;
628        let precision_radix_desc = null_i16;
629        let nullable_desc = not_null_i16;
630
631        let remarks_desc = BufferDesc::Text {
632            max_str_len: remarks_max_len,
633        };
634
635        let column_default_desc = BufferDesc::Text {
636            max_str_len: column_default_max_len,
637        };
638
639        let sql_data_type_desc = not_null_i16;
640        let sql_datetime_sub_desc = null_i16;
641        let char_octet_len_desc = null_i32;
642        let ordinal_pos_desc = BufferDesc::I32 { nullable: false };
643
644        // We expect strings to be `YES`, `NO`, or a zero-length string, so `3` should be
645        // sufficient.
646        const IS_NULLABLE_LEN_MAX_LEN: usize = 3;
647        let is_nullable_desc = BufferDesc::Text {
648            max_str_len: IS_NULLABLE_LEN_MAX_LEN,
649        };
650
651        Ok(vec![
652            catalog_name_desc,
653            schema_name_desc,
654            table_name_desc,
655            column_name_desc,
656            data_type_desc,
657            type_name_desc,
658            column_size_desc,
659            buffer_len_desc,
660            decimal_digits_desc,
661            precision_radix_desc,
662            nullable_desc,
663            remarks_desc,
664            column_default_desc,
665            sql_data_type_desc,
666            sql_datetime_sub_desc,
667            char_octet_len_desc,
668            ordinal_pos_desc,
669            is_nullable_desc,
670        ])
671    }
672
673    fn allocate_statement(&self) -> Result<StatementImpl<'_>, Error> {
674        self.connection
675            .allocate_statement()
676            .into_result(&self.connection)
677    }
678}
679
680/// Implement `Debug` for [`Connection`], in order to play nice with derive Debugs for struct
681/// holding a [`Connection`].
682impl Debug for Connection<'_> {
683    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684        write!(f, "Connection")
685    }
686}
687
688/// Options to be passed then opening a connection to a datasource.
689#[derive(Default, Clone, Copy)]
690pub struct ConnectionOptions {
691    /// Number of seconds to wait for a login request to complete before returning to the
692    /// application. The default is driver-dependent. If `0` the timeout is disabled and a
693    /// connection attempt will wait indefinitely.
694    ///
695    /// If the specified timeout exceeds the maximum login timeout in the data source, the driver
696    /// substitutes that value and uses the maximum login timeout instead.
697    ///
698    /// This corresponds to the `SQL_ATTR_LOGIN_TIMEOUT` attribute in the ODBC specification.
699    ///
700    /// See:
701    /// <https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetconnectattr-function>
702    pub login_timeout_sec: Option<u32>,
703    /// Packet size in bytes. Not all drivers support this option.
704    pub packet_size: Option<u32>,
705}
706
707impl ConnectionOptions {
708    /// Set the attributes corresponding to the connection options to an allocated connection
709    /// handle. Usually you would rather provide the options then creating the connection with e.g.
710    /// [`crate::Environment::connect_with_connection_string`] rather than calling this method
711    /// yourself.
712    pub fn apply(&self, handle: &handles::Connection) -> Result<(), Error> {
713        if let Some(timeout) = self.login_timeout_sec {
714            handle.set_login_timeout_sec(timeout).into_result(handle)?;
715        }
716        if let Some(packet_size) = self.packet_size {
717            handle.set_packet_size(packet_size).into_result(handle)?;
718        }
719        Ok(())
720    }
721}
722
723/// You can use this method to escape a password so it is suitable to be appended to an ODBC
724/// connection string as the value for the `PWD` attribute. This method is only of interest for
725/// application in need to create their own connection strings.
726///
727/// See:
728///
729/// * <https://stackoverflow.com/questions/22398212/escape-semicolon-in-odbc-connection-string-in-app-config-file>
730/// * <https://docs.microsoft.com/en-us/dotnet/api/system.data.odbc.odbcconnection.connectionstring>
731///
732/// # Example
733///
734/// ```
735/// use odbc_api::escape_attribute_value;
736///
737/// let password = "abc;123}";
738/// let user = "SA";
739/// let mut connection_string_without_credentials =
740///     "Driver={ODBC Driver 18 for SQL Server};Server=localhost;";
741///
742/// let connection_string = format!(
743///     "{}UID={};PWD={};",
744///     connection_string_without_credentials,
745///     user,
746///     escape_attribute_value(password)
747/// );
748///
749/// assert_eq!(
750///     "Driver={ODBC Driver 18 for SQL Server};Server=localhost;UID=SA;PWD={abc;123}}};",
751///     connection_string
752/// );
753/// ```
754///
755/// ```
756/// use odbc_api::escape_attribute_value;
757/// assert_eq!("abc", escape_attribute_value("abc"));
758/// assert_eq!("ab}c", escape_attribute_value("ab}c"));
759/// assert_eq!("{ab;c}", escape_attribute_value("ab;c"));
760/// assert_eq!("{a}}b;c}", escape_attribute_value("a}b;c"));
761/// assert_eq!("{ab+c}", escape_attribute_value("ab+c"));
762/// ```
763pub fn escape_attribute_value(unescaped: &str) -> Cow<'_, str> {
764    // Search the string for semicolon (';') if we do not find any, nothing is to do and we can work
765    // without an extra allocation.
766    //
767    // * We escape ';' because it serves as a separator between key=value pairs
768    // * We escape '+' because passwords with `+` must be escaped on PostgreSQL for some reason.
769    if unescaped.contains(&[';', '+'][..]) {
770        // Surround the string with curly braces ('{','}') and escape every closing curly brace by
771        // repeating it.
772        let escaped = unescaped.replace('}', "}}");
773        Cow::Owned(format!("{{{escaped}}}"))
774    } else {
775        Cow::Borrowed(unescaped)
776    }
777}
778
779/// An error type wrapping an [`Error`] and a [`Connection`]. It is used by
780/// [`Connection::into_cursor`], so that in case of failure the user can reuse the connection to try
781/// again. [`Connection::into_cursor`] could achieve the same by returning a tuple in case of an
782/// error, but this type causes less friction in most scenarios because [`Error`] implements
783/// [`From`] [`ConnectionAndError`] and it therfore works with the question mark operater (`?`).
784#[derive(Debug)]
785pub struct ConnectionAndError<'conn> {
786    pub error: Error,
787    pub connection: Connection<'conn>,
788}
789
790impl From<ConnectionAndError<'_>> for Error {
791    fn from(value: ConnectionAndError) -> Self {
792        value.error
793    }
794}
795
796impl Display for ConnectionAndError<'_> {
797    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
798        write!(f, "{}", self.error)
799    }
800}
801
802impl std::error::Error for ConnectionAndError<'_> {
803    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
804        self.error.source()
805    }
806}