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