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}