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