diff --git a/odbc-api/src/connection.rs b/odbc-api/src/connection.rs index f1bbf91e..b03fe63b 100644 --- a/odbc-api/src/connection.rs +++ b/odbc-api/src/connection.rs @@ -159,7 +159,12 @@ impl<'c> Connection<'c> { sleep: impl Sleep, ) -> Result>>, Error> { let query = SqlText::new(query); - let lazy_statement = move || self.allocate_statement(); + let lazy_statement = move || { + self.allocate_statement().and_then(|mut stmt| { + stmt.set_async_enable(true).into_result(&stmt)?; + Ok(stmt) + }) + }; execute_with_parameters_polling(lazy_statement, Some(&query), params, sleep).await } diff --git a/odbc-api/src/cursor.rs b/odbc-api/src/cursor.rs index c9cd5c45..d5bbcb09 100644 --- a/odbc-api/src/cursor.rs +++ b/odbc-api/src/cursor.rs @@ -6,12 +6,13 @@ use crate::{ handles::{AsStatementRef, CDataMut, SqlResult, State, Statement, StatementRef}, parameter::{Binary, CElement, Text, VarCell, VarKind, WideText}, sleep::{wait_for, Sleep}, - Error, ResultSetMetadata, + AsyncResultSetMetadata, Error, ResultSetMetadata, }; use std::{ mem::{size_of, MaybeUninit}, ptr, + sync::{Arc, Mutex}, thread::panicking, }; @@ -314,6 +315,7 @@ where } impl ResultSetMetadata for CursorImpl where S: AsStatementRef {} +impl AsyncResultSetMetadata for CursorPolling where S: AsStatementRef {} impl Cursor for CursorImpl where diff --git a/odbc-api/src/handles/statement.rs b/odbc-api/src/handles/statement.rs index 35162ef3..98b70491 100644 --- a/odbc-api/src/handles/statement.rs +++ b/odbc-api/src/handles/statement.rs @@ -12,9 +12,9 @@ use super::{ use log::debug; use odbc_sys::{ Desc, FreeStmtOption, HDbc, HStmt, Handle, HandleType, Len, ParamType, Pointer, SQLBindCol, - SQLBindParameter, SQLCloseCursor, SQLDescribeParam, SQLExecute, SQLFetch, SQLFreeStmt, - SQLGetData, SQLMoreResults, SQLNumParams, SQLNumResultCols, SQLParamData, SQLPutData, - SQLRowCount, SqlDataType, SqlReturn, StatementAttribute, IS_POINTER, + SQLBindParameter, SQLCancel, SQLCloseCursor, SQLDescribeParam, SQLExecute, SQLFetch, + SQLFreeStmt, SQLGetData, SQLMoreResults, SQLNumParams, SQLNumResultCols, SQLParamData, + SQLPutData, SQLRowCount, SqlDataType, SqlReturn, StatementAttribute, IS_POINTER, }; use std::{ffi::c_void, marker::PhantomData, mem::ManuallyDrop, num::NonZeroUsize, ptr::null_mut}; @@ -157,6 +157,28 @@ impl<'s> AsStatementRef for StatementRef<'s> { /// The trait allows us to reason about statements without taking the lifetime of their connection /// into account. It also allows for the trait to be implemented by a handle taking ownership of /// both, the statement and the connection. +/// +/// +/// --------------- Notes on Asynchronous Execution --------------- +/// Additionally, it's important to note that while a function is executing asynchronously, the application can call functions on any other statements. The application can also call functions on any connection, except the one associated with the asynchronous statement. +// However, the application can only call the original function and the following functions (with the statement handle or its associated connection, environment handle), after a statement operation returns SQL_STILL_EXECUTING: + +// SQLCancel +// SQLCancelHandle (on the statement handle) +// SQLGetDiagField +// SQLGetDiagRec +// SQLAllocHandle +// SQLGetEnvAttr +// SQLGetConnectAttr +// SQLDataSources +// SQLDrivers +// SQLGetInfo +// SQLGetFunctions +// SQLNativeSql + +// These functions can be called simultaneously with an asynchronous function that is being polled. +// The following statement functions operate on a data source and can execute asynchronously: +// SQLBulkOperations, SQLColAttribute, SQLColumnPrivileges, SQLColumns, SQLDescribeCol, SQLDescribeParam, SQLExecDirect, SQLExecute, SQLFetch, SQLFetchScroll, SQLForeignKeys, SQLGetData, SQLGetTypeInfo, SQLMoreResults, SQLNumParams, SQLNumResultCols, SQLParamData, SQLPrepare, SQLPrimaryKeys, SQLProcedureColumns, SQLProcedures, SQLPutData, SQLSetPos, SQLSpecialColumns, SQLStatistics, SQLTablePrivileges, SQLTables pub trait Statement: AsHandle { /// Gain access to the underlying statement handle without transferring ownership to it. fn as_sys(&self) -> HStmt; @@ -202,11 +224,15 @@ pub trait Statement: AsHandle { /// # Safety /// /// Fetch dereferences bound column pointers. + /// + /// Note: This function can be called asynchronously per Microsoft docs. unsafe fn fetch(&mut self) -> SqlResult<()> { SQLFetch(self.as_sys()).into_sql_result("SQLFetch") } /// Retrieves data for a single column in the result set or for a single parameter. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn get_data(&mut self, col_or_param_num: u16, target: &mut impl CDataMut) -> SqlResult<()> { unsafe { SQLGetData( @@ -259,6 +285,11 @@ pub trait Statement: AsHandle { } } + /// Can be called concurrently on the same stmt as a another statement that is being executed asynchronously. + fn cancel(&mut self) -> SqlResult<()> { + unsafe { SQLCancel(self.as_sys()) }.into_sql_result("SQLCancel") + } + /// Fetch a column description using the column index. /// /// # Parameters @@ -268,6 +299,8 @@ pub trait Statement: AsHandle { /// * `column_description`: Holds the description of the column after the call. This method does /// not provide strong exception safety as the value of this argument is undefined in case of an /// error. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn describe_col( &self, column_number: u16, @@ -330,6 +363,7 @@ pub trait Statement: AsHandle { /// * [`SqlResult::NeedData`] if execution requires additional data from delayed parameters. /// * [`SqlResult::NoData`] if a searched update or delete statement did not affect any rows at /// the data source. + /// Note: This function can be called asynchronously per Microsoft docs. unsafe fn exec_direct(&mut self, statement: &SqlText) -> SqlResult<()> { sql_exec_direc( self.as_sys(), @@ -347,6 +381,8 @@ pub trait Statement: AsHandle { /// Send an SQL statement to the data source for preparation. The application can include one or /// more parameter markers in the SQL statement. To include a parameter marker, the application /// embeds a question mark (?) into the SQL string at the appropriate position. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn prepare(&mut self, statement: &SqlText) -> SqlResult<()> { unsafe { sql_prepare( @@ -374,6 +410,7 @@ pub trait Statement: AsHandle { /// * [`SqlResult::NeedData`] if execution requires additional data from delayed parameters. /// * [`SqlResult::NoData`] if a searched update or delete statement did not affect any rows at /// the data source. + /// Note: This function can be called asynchronously per Microsoft docs. unsafe fn execute(&mut self) -> SqlResult<()> { SQLExecute(self.as_sys()).into_sql_result("SQLExecute") } @@ -381,6 +418,8 @@ pub trait Statement: AsHandle { /// Number of columns in result set. /// /// Can also be used to check, whether or not a result set has been created at all. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn num_result_cols(&self) -> SqlResult { let mut out: i16 = 0; unsafe { SQLNumResultCols(self.as_sys(), &mut out) } @@ -389,6 +428,8 @@ pub trait Statement: AsHandle { } /// Number of placeholders of a prepared query. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn num_params(&self) -> SqlResult { let mut out: i16 = 0; unsafe { SQLNumParams(self.as_sys(), &mut out) } @@ -596,6 +637,8 @@ pub trait Statement: AsHandle { /// otherwise. /// /// `column_number`: Index of the column, starting at 1. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn is_unsigned_column(&self, column_number: u16) -> SqlResult { unsafe { self.numeric_col_attribute(Desc::Unsigned, column_number) }.map(|out| match out { 0 => false, @@ -607,6 +650,8 @@ pub trait Statement: AsHandle { /// Returns a number identifying the SQL type of the column in the result set. /// /// `column_number`: Index of the column, starting at 1. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn col_type(&self, column_number: u16) -> SqlResult { unsafe { self.numeric_col_attribute(Desc::Type, column_number) }.map(|ret| { SqlDataType(ret.try_into().expect( @@ -623,6 +668,8 @@ pub trait Statement: AsHandle { /// concise data type; for example, `TIME` or `INTERVAL_YEAR`. /// /// `column_number`: Index of the column, starting at 1. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn col_concise_type(&self, column_number: u16) -> SqlResult { unsafe { self.numeric_col_attribute(Desc::ConciseType, column_number) }.map(|ret| { SqlDataType(ret.try_into().expect( @@ -639,6 +686,8 @@ pub trait Statement: AsHandle { /// returned, excluding a terminating zero. /// /// `column_number`: Index of the column, starting at 1. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn col_octet_length(&self, column_number: u16) -> SqlResult { unsafe { self.numeric_col_attribute(Desc::OctetLength, column_number) } } @@ -646,6 +695,8 @@ pub trait Statement: AsHandle { /// Maximum number of characters required to display data from the column. /// /// `column_number`: Index of the column, starting at 1. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn col_display_size(&self, column_number: u16) -> SqlResult { unsafe { self.numeric_col_attribute(Desc::DisplaySize, column_number) } } @@ -655,18 +706,24 @@ pub trait Statement: AsHandle { /// Denotes the applicable precision. For data types SQL_TYPE_TIME, SQL_TYPE_TIMESTAMP, and all /// the interval data types that represent a time interval, its value is the applicable /// precision of the fractional seconds component. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn col_precision(&self, column_number: u16) -> SqlResult { unsafe { self.numeric_col_attribute(Desc::Precision, column_number) } } /// The applicable scale for a numeric data type. For DECIMAL and NUMERIC data types, this is /// the defined scale. It is undefined for all other data types. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn col_scale(&self, column_number: u16) -> SqlResult { unsafe { self.numeric_col_attribute(Desc::Scale, column_number) } } /// The column alias, if it applies. If the column alias does not apply, the column name is /// returned. If there is no column name or a column alias, an empty string is returned. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn col_name(&self, column_number: u16, buffer: &mut Vec) -> SqlResult<()> { // String length in bytes, not characters. Terminating zero is excluded. let mut string_length_in_bytes: i16 = 0; @@ -752,6 +809,8 @@ pub trait Statement: AsHandle { /// /// * `parameter_number`: Parameter marker number ordered sequentially in increasing parameter /// order, starting at 1. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn describe_param(&self, parameter_number: u16) -> SqlResult { let mut data_type = SqlDataType::UNKNOWN_TYPE; let mut parameter_size = 0; @@ -779,6 +838,8 @@ pub trait Statement: AsHandle { /// [`crate::sys::len_data_at_exec`]. /// /// Return value contains a parameter identifier passed to bind parameter as a value pointer. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn param_data(&mut self) -> SqlResult> { unsafe { let mut param_id: Pointer = null_mut(); @@ -791,6 +852,8 @@ pub trait Statement: AsHandle { } /// Executes a columns query using this statement handle. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn columns( &mut self, catalog_name: &SqlText, @@ -820,6 +883,8 @@ pub trait Statement: AsHandle { /// The catalog, schema and table parameters are search patterns by default unless /// [`Self::set_metadata_id`] is called with `true`. In that case they must also not be `None` /// since otherwise a NulPointer error is emitted. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn tables( &mut self, catalog_name: &SqlText, @@ -847,6 +912,8 @@ pub trait Statement: AsHandle { /// of foreign keys in other table that refer to the primary key of the specified table. /// /// Like [`Self::tables`] this changes the statement to a cursor over the result set. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn foreign_keys( &mut self, pk_catalog_name: &SqlText, @@ -880,6 +947,8 @@ pub trait Statement: AsHandle { /// [`SqlResult::NeedData`] /// /// Panics if batch is empty. + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn put_binary_batch(&mut self, batch: &[u8]) -> SqlResult<()> { // Probably not strictly necessary. MSSQL returns an error than inserting empty batches. // Still strikes me as a programming error. Maybe we could also do nothing instead. @@ -903,6 +972,8 @@ pub trait Statement: AsHandle { /// /// /// + /// + /// Note: This function can be called asynchronously per Microsoft docs. fn row_count(&self) -> SqlResult { let mut ret = 0isize; unsafe { @@ -946,6 +1017,8 @@ pub trait Statement: AsHandle { /// /// Since a different result set might have a different schema, care needs to be taken that /// bound buffers are used correctly. + /// + /// Note: This function can be called asynchronously per Microsoft docs. unsafe fn more_results(&mut self) -> SqlResult<()> { unsafe { SQLMoreResults(self.as_sys()).into_sql_result("SQLMoreResults") } } diff --git a/odbc-api/src/lib.rs b/odbc-api/src/lib.rs index 907aaa41..f7d20363 100644 --- a/odbc-api/src/lib.rs +++ b/odbc-api/src/lib.rs @@ -50,7 +50,7 @@ pub use self::{ parameter_collection::{ParameterCollection, ParameterCollectionRef, ParameterTupleElement}, preallocated::{Preallocated, PreallocatedPolling}, prepared::Prepared, - result_set_metadata::ResultSetMetadata, + result_set_metadata::{AsyncResultSetMetadata, ResultSetMetadata}, sleep::Sleep, statement_connection::StatementConnection, }; diff --git a/odbc-api/src/prepared.rs b/odbc-api/src/prepared.rs index b58ed465..25d700dc 100644 --- a/odbc-api/src/prepared.rs +++ b/odbc-api/src/prepared.rs @@ -2,6 +2,7 @@ use crate::{ buffers::{AnyBuffer, BufferDesc, ColumnBuffer, TextColumn}, execute::execute_with_parameters, handles::{AsStatementRef, HasDataType, ParameterDescription, Statement, StatementRef}, + result_set_metadata::AsyncResultSetMetadata, ColumnarBulkInserter, CursorImpl, Error, ParameterCollectionRef, ResultSetMetadata, }; diff --git a/odbc-api/src/result_set_metadata.rs b/odbc-api/src/result_set_metadata.rs index ef41f964..10878aa4 100644 --- a/odbc-api/src/result_set_metadata.rs +++ b/odbc-api/src/result_set_metadata.rs @@ -4,9 +4,246 @@ use odbc_sys::SqlDataType; use crate::{ handles::{slice_to_utf8, AsStatementRef, SqlChar, Statement}, - ColumnDescription, DataType, Error, + sleep::{self, wait_for}, + ColumnDescription, DataType, Error, Sleep, }; +/// Provides Metadata of the resulting the result set. Implemented by `Cursor` types and prepared +/// queries. Fetching metadata from a prepared query might be expensive (driver dependent), so your +/// application should fetch the Metadata it requires from the `Cursor` if possible. +/// +/// See also: +/// + +// note that we know that this trait won't be Send, so we suppress the warning +#[allow(async_fn_in_trait)] +pub trait AsyncResultSetMetadata: AsStatementRef { + /// Fetch a column description using the column index. + /// + /// # Parameters + /// + /// * `column_number`: Column index. `0` is the bookmark column. The other column indices start + /// with `1`. + /// * `column_description`: Holds the description of the column after the call. This method does + /// not provide strong exception safety as the value of this argument is undefined in case of an + /// error. + async fn describe_col( + &mut self, + column_number: u16, + column_description: &mut ColumnDescription, + mut sleep: impl Sleep, + ) -> Result<(), Error> { + let stmt = self.as_stmt_ref(); + wait_for( + || stmt.describe_col(column_number, column_description), + &mut sleep, + ) + .await + .into_result(&stmt) + } + + /// Number of columns in result set. Can also be used to see whether executing a prepared + /// Statement ([`crate::Prepared`]) would yield a result set, as this would return `0` if it + /// does not. + /// + /// See also: + /// + async fn num_result_cols(&mut self, mut sleep: impl Sleep) -> Result { + let stmt = self.as_stmt_ref(); + let res = wait_for(|| stmt.num_result_cols(), &mut sleep).await; + res.into_result(&stmt) + } + + /// `true` if a given column in a result set is unsigned or not a numeric type, `false` + /// otherwise. + /// + /// `column_number`: Index of the column, starting at 1. + async fn column_is_unsigned( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result { + let stmt = self.as_stmt_ref(); + wait_for(|| stmt.is_unsigned_column(column_number), &mut sleep) + .await + .into_result(&stmt) + } + + /// Size in bytes of the columns. For variable sized types this is the maximum size, excluding a + /// terminating zero. + /// + /// `column_number`: Index of the column, starting at 1. + async fn col_octet_length( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result, Error> { + let stmt = self.as_stmt_ref(); + wait_for(|| stmt.col_octet_length(column_number), &mut sleep) + .await + .into_result(&stmt) + .map(|signed| NonZeroUsize::new(signed.max(0) as usize)) + } + + /// Maximum number of characters required to display data from the column. If the driver is + /// unable to provide a maximum `None` is returned. + /// + /// `column_number`: Index of the column, starting at 1. + async fn col_display_size( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result, Error> { + let stmt = self.as_stmt_ref(); + wait_for(|| stmt.col_display_size(column_number), &mut sleep) + .await + .into_result(&stmt) + // Map negative values to `0`. `0` is used by MSSQL to indicate a missing upper bound + // `-4` (`NO_TOTAL`) is used by MySQL to do the same. Mapping them both to the same + // value allows for less error prone generic applications. Making this value `None` + // instead of zero makes it explicit, that an upper bound can not always be known. It + // also prevents the order from being misunderstood, because the largest possible value + // is obviously `> 0` in this case, yet `0` is smaller than any other value. + .map(|signed| NonZeroUsize::new(signed.max(0) as usize)) + } + + /// Precision of the column. + /// + /// Denotes the applicable precision. For data types SQL_TYPE_TIME, SQL_TYPE_TIMESTAMP, and all + /// the interval data types that represent a time interval, its value is the applicable + /// precision of the fractional seconds component. + async fn col_precision( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result { + let stmt = self.as_stmt_ref(); + wait_for(|| stmt.col_precision(column_number), &mut sleep) + .await + .into_result(&stmt) + } + + /// The applicable scale for a numeric data type. For DECIMAL and NUMERIC data types, this is + /// the defined scale. It is undefined for all other data types. + fn col_scale(&mut self, column_number: u16) -> Result { + let stmt = self.as_stmt_ref(); + stmt.col_scale(column_number).into_result(&stmt) + } + + /// The column alias, if it applies. If the column alias does not apply, the column name is + /// returned. If there is no column name or a column alias, an empty string is returned. + fn col_name(&mut self, column_number: u16) -> Result { + let stmt = self.as_stmt_ref(); + let mut buf = vec![0; 1024]; + stmt.col_name(column_number, &mut buf).into_result(&stmt)?; + Ok(slice_to_utf8(&buf).unwrap()) + } + + /// Use this if you want to iterate over all column names and allocate a `String` for each one. + /// + /// This is a wrapper around `col_name` introduced for convenience. + async fn column_names(&mut self, sleep: impl Sleep) -> Result, Error> { + ColumnNamesIt::new_async(self, sleep).await + } + + /// Data type of the specified column. + /// + /// `column_number`: Index of the column, starting at 1. + async fn col_data_type( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result { + let stmt = self.as_stmt_ref(); + let kind = wait_for(|| stmt.col_concise_type(column_number), &mut sleep) + .await + .into_result(&stmt)?; + let dt = match kind { + SqlDataType::UNKNOWN_TYPE => DataType::Unknown, + SqlDataType::EXT_VAR_BINARY => DataType::Varbinary { + length: self.col_octet_length(column_number, sleep).await?, + }, + SqlDataType::EXT_LONG_VAR_BINARY => DataType::LongVarbinary { + length: self.col_octet_length(column_number, sleep).await?, + }, + SqlDataType::EXT_BINARY => DataType::Binary { + length: self.col_octet_length(column_number, sleep).await?, + }, + SqlDataType::EXT_W_VARCHAR => DataType::WVarchar { + length: self.col_display_size(column_number, sleep).await?, + }, + SqlDataType::EXT_W_CHAR => DataType::WChar { + length: self.col_display_size(column_number, sleep).await?, + }, + SqlDataType::EXT_LONG_VARCHAR => DataType::LongVarchar { + length: self.col_display_size(column_number, sleep).await?, + }, + SqlDataType::CHAR => DataType::Char { + length: self.col_display_size(column_number, sleep).await?, + }, + SqlDataType::VARCHAR => DataType::Varchar { + length: self.col_display_size(column_number, sleep).await?, + }, + SqlDataType::NUMERIC => DataType::Numeric { + precision: self + .col_precision(column_number, sleep) + .await? + .try_into() + .unwrap(), + scale: self.col_scale(column_number)?.try_into().unwrap(), + }, + SqlDataType::DECIMAL => DataType::Decimal { + precision: self + .col_precision(column_number, sleep) + .await? + .try_into() + .unwrap(), + scale: self.col_scale(column_number)?.try_into().unwrap(), + }, + SqlDataType::INTEGER => DataType::Integer, + SqlDataType::SMALLINT => DataType::SmallInt, + SqlDataType::FLOAT => DataType::Float { + precision: self + .col_precision(column_number, sleep) + .await? + .try_into() + .unwrap(), + }, + SqlDataType::REAL => DataType::Real, + SqlDataType::DOUBLE => DataType::Double, + SqlDataType::DATE => DataType::Date, + SqlDataType::TIME => DataType::Time { + precision: self + .col_precision(column_number, sleep) + .await? + .try_into() + .unwrap(), + }, + SqlDataType::TIMESTAMP => DataType::Timestamp { + precision: self + .col_precision(column_number, sleep) + .await? + .try_into() + .unwrap(), + }, + SqlDataType::EXT_BIG_INT => DataType::BigInt, + SqlDataType::EXT_TINY_INT => DataType::TinyInt, + SqlDataType::EXT_BIT => DataType::Bit, + other => { + let mut column_description = ColumnDescription::default(); + self.describe_col(column_number, &mut column_description, sleep) + .await?; + DataType::Other { + data_type: other, + column_size: column_description.data_type.column_size(), + decimal_digits: column_description.data_type.decimal_digits(), + } + } + }; + Ok(dt) + } +} + /// Provides Metadata of the resulting the result set. Implemented by `Cursor` types and prepared /// queries. Fetching metadata from a prepared query might be expensive (driver dependent), so your /// application should fetch the Metadata it requires from the `Cursor` if possible. @@ -235,6 +472,21 @@ impl<'c, C: ResultSetMetadata + ?Sized> ColumnNamesIt<'c, C> { } } +impl<'c, C: AsyncResultSetMetadata + ?Sized> ColumnNamesIt<'c, C> { + async fn new_async(cursor: &'c mut C, sleep: impl Sleep) -> Result { + let num_cols = cursor.num_result_cols(sleep).await?.try_into().unwrap(); + Ok(Self { + cursor, + // Some ODBC drivers do not report the required size to hold the column name. Starting + // with a reasonable sized buffers, allows us to fetch reasonable sized column alias + // even from those. + buffer: Vec::with_capacity(128), + num_cols, + column: 1, + }) + } +} + impl Iterator for ColumnNamesIt<'_, C> where C: ResultSetMetadata, diff --git a/odbc-api/src/sleep.rs b/odbc-api/src/sleep.rs index 866f0472..291838f6 100644 --- a/odbc-api/src/sleep.rs +++ b/odbc-api/src/sleep.rs @@ -1,5 +1,7 @@ use std::future::Future; +use log::info; + use crate::handles::SqlResult; /// Governs the behaviour of of polling in async functions. @@ -33,6 +35,7 @@ where let mut ret = (f)(); // Wait for operation to finish, using polling method while matches!(ret, SqlResult::StillExecuting) { + info!("Waiting for operation to finish."); sleep.next_poll().await; ret = (f)(); }