From e8427aa6112c498c5d51084b7500871e86d16b3e Mon Sep 17 00:00:00 2001 From: Matthew Gapp <61894094+matthewgapp@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:36:48 -0700 Subject: [PATCH 1/7] wip --- Cargo.lock | 24 ++++ odbc-api/Cargo.toml | 2 + odbc-api/src/cursor.rs | 2 + odbc-api/src/execute.rs | 21 ++- odbc-api/src/handles/statement.rs | 10 +- odbc-api/src/result_set_metadata.rs | 204 +++++++++++++++++++++++++++- odbc-api/src/sleep.rs | 25 +++- 7 files changed, 280 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2877431..275f3815 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atoi" version = "2.0.0" @@ -1106,6 +1117,7 @@ name = "odbc-api" version = "8.1.1" dependencies = [ "anyhow", + "async-trait", "atoi", "criterion", "csv", @@ -1119,6 +1131,7 @@ dependencies = [ "test-case", "thiserror", "tokio", + "trait-variant", "widestring", "winit", ] @@ -1634,6 +1647,17 @@ version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/odbc-api/Cargo.toml b/odbc-api/Cargo.toml index f26db5af..594173d0 100644 --- a/odbc-api/Cargo.toml +++ b/odbc-api/Cargo.toml @@ -77,6 +77,8 @@ log = "0.4.22" widestring = "1.1.0" atoi = "2.0.0" odbc-api-derive ={ version = "8.1.1", path = "../derive", optional = true} +trait-variant = "0.1.2" +async-trait = "0.1.81" [target.'cfg(windows)'.dependencies] # We use winit to display dialogs prompting for connection strings. We can deactivate default diff --git a/odbc-api/src/cursor.rs b/odbc-api/src/cursor.rs index c9cd5c45..fdf4c378 100644 --- a/odbc-api/src/cursor.rs +++ b/odbc-api/src/cursor.rs @@ -12,6 +12,7 @@ use crate::{ 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 ResultSetMetadata for CursorPolling where S: AsStatementRef {} impl Cursor for CursorImpl where diff --git a/odbc-api/src/execute.rs b/odbc-api/src/execute.rs index 270a5bae..50765588 100644 --- a/odbc-api/src/execute.rs +++ b/odbc-api/src/execute.rs @@ -1,9 +1,9 @@ -use std::intrinsics::transmute; +use std::{intrinsics::transmute, time::Duration}; use crate::{ handles::{AsStatementRef, SqlText, Statement}, parameter::Blob, - sleep::wait_for, + sleep::{wait_for, wait_for_with_cancel}, CursorImpl, CursorPolling, Error, ParameterCollectionRef, Sleep, }; @@ -146,7 +146,22 @@ where let mut stmt = statement.as_stmt_ref(); let result = if let Some(sql) = query { // We execute an unprepared "one shot query" - wait_for(|| stmt.exec_direct(sql), &mut sleep).await + wait_for_with_cancel( + |should_cancel| { + println!("should_cancel: {:?}", should_cancel); + if should_cancel { + stmt.cancel() + } else { + println!("exec_direct"); + let res = stmt.exec_direct(sql); + println!("done exec_direct"); + res + } + }, + &mut sleep, + |duration| duration > Duration::from_secs(30), + ) + .await } else { // We execute a prepared query wait_for(|| stmt.execute(), &mut sleep).await diff --git a/odbc-api/src/handles/statement.rs b/odbc-api/src/handles/statement.rs index 35162ef3..63efec23 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}; @@ -259,6 +259,10 @@ pub trait Statement: AsHandle { } } + fn cancel(&mut self) -> SqlResult<()> { + unsafe { SQLCancel(self.as_sys()) }.into_sql_result("SQLCancel") + } + /// Fetch a column description using the column index. /// /// # Parameters diff --git a/odbc-api/src/result_set_metadata.rs b/odbc-api/src/result_set_metadata.rs index ef41f964..b04ebadb 100644 --- a/odbc-api/src/result_set_metadata.rs +++ b/odbc-api/src/result_set_metadata.rs @@ -4,9 +4,196 @@ 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: +/// + +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. + fn describe_col( + &mut self, + column_number: u16, + column_description: &mut ColumnDescription, + ) -> Result<(), Error> { + let stmt = self.as_stmt_ref(); + stmt.describe_col(column_number, column_description) + .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. + fn col_octet_length(&mut self, column_number: u16) -> Result, Error> { + let stmt = self.as_stmt_ref(); + stmt.col_octet_length(column_number) + .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. + fn col_display_size(&mut self, column_number: u16) -> Result, Error> { + let stmt = self.as_stmt_ref(); + stmt.col_display_size(column_number) + .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. + fn col_precision(&mut self, column_number: u16) -> Result { + let stmt = self.as_stmt_ref(); + stmt.col_precision(column_number).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) -> Result, Error> { + ColumnNamesIt::new_async(self).await + } + + /// Data type of the specified column. + /// + /// `column_number`: Index of the column, starting at 1. + fn col_data_type(&mut self, column_number: u16) -> Result { + let stmt = self.as_stmt_ref(); + let kind = stmt.col_concise_type(column_number).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)?, + }, + SqlDataType::EXT_LONG_VAR_BINARY => DataType::LongVarbinary { + length: self.col_octet_length(column_number)?, + }, + SqlDataType::EXT_BINARY => DataType::Binary { + length: self.col_octet_length(column_number)?, + }, + SqlDataType::EXT_W_VARCHAR => DataType::WVarchar { + length: self.col_display_size(column_number)?, + }, + SqlDataType::EXT_W_CHAR => DataType::WChar { + length: self.col_display_size(column_number)?, + }, + SqlDataType::EXT_LONG_VARCHAR => DataType::LongVarchar { + length: self.col_display_size(column_number)?, + }, + SqlDataType::CHAR => DataType::Char { + length: self.col_display_size(column_number)?, + }, + SqlDataType::VARCHAR => DataType::Varchar { + length: self.col_display_size(column_number)?, + }, + SqlDataType::NUMERIC => DataType::Numeric { + precision: self.col_precision(column_number)?.try_into().unwrap(), + scale: self.col_scale(column_number)?.try_into().unwrap(), + }, + SqlDataType::DECIMAL => DataType::Decimal { + precision: self.col_precision(column_number)?.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)?.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)?.try_into().unwrap(), + }, + SqlDataType::TIMESTAMP => DataType::Timestamp { + precision: self.col_precision(column_number)?.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)?; + 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 +422,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) -> Result { + let num_cols = cursor.num_result_cols()?.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..4559c71b 100644 --- a/odbc-api/src/sleep.rs +++ b/odbc-api/src/sleep.rs @@ -1,4 +1,7 @@ -use std::future::Future; +use std::{ + future::Future, + time::{Duration, Instant}, +}; use crate::handles::SqlResult; @@ -38,3 +41,23 @@ where } ret } + +pub async fn wait_for_with_cancel( + mut f: F, + sleep: &mut impl Sleep, + should_cancel: impl Fn(Duration) -> bool, +) -> SqlResult +where + F: FnMut(bool) -> SqlResult, +{ + let mut ret = (f)(should_cancel(Duration::ZERO)); + + let time = Instant::now(); + + // Wait for operation to finish, using polling method + while matches!(ret, SqlResult::StillExecuting) { + sleep.next_poll().await; + ret = (f)(should_cancel(time.elapsed())); + } + ret +} From f1107780fe9b33826b970674c487220dd6181f0f Mon Sep 17 00:00:00 2001 From: Matthew Gapp <61894094+matthewgapp@users.noreply.github.com> Date: Sat, 13 Jul 2024 11:24:21 -0700 Subject: [PATCH 2/7] wip --- odbc-api/src/cursor.rs | 4 +- odbc-api/src/handles/statement.rs | 69 ++++++++++++++++++ odbc-api/src/lib.rs | 2 +- odbc-api/src/prepared.rs | 1 + odbc-api/src/result_set_metadata.rs | 106 ++++++++++++++++++++-------- 5 files changed, 150 insertions(+), 32 deletions(-) diff --git a/odbc-api/src/cursor.rs b/odbc-api/src/cursor.rs index fdf4c378..d5bbcb09 100644 --- a/odbc-api/src/cursor.rs +++ b/odbc-api/src/cursor.rs @@ -6,7 +6,7 @@ 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::{ @@ -315,7 +315,7 @@ where } impl ResultSetMetadata for CursorImpl where S: AsStatementRef {} -impl ResultSetMetadata for CursorPolling 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 63efec23..98b70491 100644 --- a/odbc-api/src/handles/statement.rs +++ b/odbc-api/src/handles/statement.rs @@ -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,7 @@ 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") } @@ -272,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, @@ -334,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(), @@ -351,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( @@ -378,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") } @@ -385,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) } @@ -393,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) } @@ -600,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, @@ -611,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( @@ -627,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( @@ -643,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) } } @@ -650,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) } } @@ -659,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; @@ -756,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; @@ -783,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(); @@ -795,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, @@ -824,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, @@ -851,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, @@ -884,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. @@ -907,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 { @@ -950,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 b04ebadb..5d876b51 100644 --- a/odbc-api/src/result_set_metadata.rs +++ b/odbc-api/src/result_set_metadata.rs @@ -25,14 +25,19 @@ pub trait AsyncResultSetMetadata: AsStatementRef { /// * `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. - fn describe_col( + 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(); - stmt.describe_col(column_number, column_description) - .into_result(&stmt) + 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 @@ -66,9 +71,14 @@ pub trait AsyncResultSetMetadata: AsStatementRef { /// terminating zero. /// /// `column_number`: Index of the column, starting at 1. - fn col_octet_length(&mut self, column_number: u16) -> Result, Error> { + async fn col_octet_length( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result, Error> { let stmt = self.as_stmt_ref(); - stmt.col_octet_length(column_number) + wait_for(|| stmt.col_octet_length(column_number), &mut sleep) + .await .into_result(&stmt) .map(|signed| NonZeroUsize::new(signed.max(0) as usize)) } @@ -77,9 +87,14 @@ pub trait AsyncResultSetMetadata: AsStatementRef { /// unable to provide a maximum `None` is returned. /// /// `column_number`: Index of the column, starting at 1. - fn col_display_size(&mut self, column_number: u16) -> Result, Error> { + async fn col_display_size( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result, Error> { let stmt = self.as_stmt_ref(); - stmt.col_display_size(column_number) + 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 @@ -95,9 +110,15 @@ pub trait AsyncResultSetMetadata: AsStatementRef { /// 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. - fn col_precision(&mut self, column_number: u16) -> Result { + async fn col_precision( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result { let stmt = self.as_stmt_ref(); - stmt.col_precision(column_number).into_result(&stmt) + 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 @@ -119,70 +140,97 @@ pub trait AsyncResultSetMetadata: AsStatementRef { /// 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) -> Result, Error> { - ColumnNamesIt::new_async(self).await + 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. - fn col_data_type(&mut self, column_number: u16) -> Result { + async fn col_data_type( + &mut self, + column_number: u16, + mut sleep: impl Sleep, + ) -> Result { let stmt = self.as_stmt_ref(); - let kind = stmt.col_concise_type(column_number).into_result(&stmt)?; + 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)?, + length: self.col_octet_length(column_number, sleep).await?, }, SqlDataType::EXT_LONG_VAR_BINARY => DataType::LongVarbinary { - length: self.col_octet_length(column_number)?, + length: self.col_octet_length(column_number, sleep).await?, }, SqlDataType::EXT_BINARY => DataType::Binary { - length: self.col_octet_length(column_number)?, + length: self.col_octet_length(column_number, sleep).await?, }, SqlDataType::EXT_W_VARCHAR => DataType::WVarchar { - length: self.col_display_size(column_number)?, + length: self.col_display_size(column_number, sleep).await?, }, SqlDataType::EXT_W_CHAR => DataType::WChar { - length: self.col_display_size(column_number)?, + length: self.col_display_size(column_number, sleep).await?, }, SqlDataType::EXT_LONG_VARCHAR => DataType::LongVarchar { - length: self.col_display_size(column_number)?, + length: self.col_display_size(column_number, sleep).await?, }, SqlDataType::CHAR => DataType::Char { - length: self.col_display_size(column_number)?, + length: self.col_display_size(column_number, sleep).await?, }, SqlDataType::VARCHAR => DataType::Varchar { - length: self.col_display_size(column_number)?, + length: self.col_display_size(column_number, sleep).await?, }, SqlDataType::NUMERIC => DataType::Numeric { - precision: self.col_precision(column_number)?.try_into().unwrap(), + 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)?.try_into().unwrap(), + 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)?.try_into().unwrap(), + 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)?.try_into().unwrap(), + precision: self + .col_precision(column_number, sleep) + .await? + .try_into() + .unwrap(), }, SqlDataType::TIMESTAMP => DataType::Timestamp { - precision: self.col_precision(column_number)?.try_into().unwrap(), + 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)?; + self.describe_col(column_number, &mut column_description, sleep) + .await?; DataType::Other { data_type: other, column_size: column_description.data_type.column_size(), @@ -423,8 +471,8 @@ impl<'c, C: ResultSetMetadata + ?Sized> ColumnNamesIt<'c, C> { } impl<'c, C: AsyncResultSetMetadata + ?Sized> ColumnNamesIt<'c, C> { - async fn new_async(cursor: &'c mut C) -> Result { - let num_cols = cursor.num_result_cols()?.try_into().unwrap(); + 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 From dc9f404704fe0c262b55a5d1b9a06add4d4d2564 Mon Sep 17 00:00:00 2001 From: Matthew Gapp <61894094+matthewgapp@users.noreply.github.com> Date: Sat, 13 Jul 2024 12:37:07 -0700 Subject: [PATCH 3/7] wip: getting function sequence error --- odbc-api/src/connection.rs | 7 ++++++- odbc-api/src/execute.rs | 19 ++----------------- odbc-api/src/sleep.rs | 23 +++-------------------- 3 files changed, 11 insertions(+), 38 deletions(-) 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/execute.rs b/odbc-api/src/execute.rs index 50765588..b621bc96 100644 --- a/odbc-api/src/execute.rs +++ b/odbc-api/src/execute.rs @@ -3,7 +3,7 @@ use std::{intrinsics::transmute, time::Duration}; use crate::{ handles::{AsStatementRef, SqlText, Statement}, parameter::Blob, - sleep::{wait_for, wait_for_with_cancel}, + sleep::wait_for, CursorImpl, CursorPolling, Error, ParameterCollectionRef, Sleep, }; @@ -146,22 +146,7 @@ where let mut stmt = statement.as_stmt_ref(); let result = if let Some(sql) = query { // We execute an unprepared "one shot query" - wait_for_with_cancel( - |should_cancel| { - println!("should_cancel: {:?}", should_cancel); - if should_cancel { - stmt.cancel() - } else { - println!("exec_direct"); - let res = stmt.exec_direct(sql); - println!("done exec_direct"); - res - } - }, - &mut sleep, - |duration| duration > Duration::from_secs(30), - ) - .await + wait_for(|| stmt.exec_direct(sql), &mut sleep).await } else { // We execute a prepared query wait_for(|| stmt.execute(), &mut sleep).await diff --git a/odbc-api/src/sleep.rs b/odbc-api/src/sleep.rs index 4559c71b..e203d10b 100644 --- a/odbc-api/src/sleep.rs +++ b/odbc-api/src/sleep.rs @@ -3,6 +3,8 @@ use std::{ time::{Duration, Instant}, }; +use log::info; + use crate::handles::SqlResult; /// Governs the behaviour of of polling in async functions. @@ -36,28 +38,9 @@ where let mut ret = (f)(); // Wait for operation to finish, using polling method while matches!(ret, SqlResult::StillExecuting) { + info!("Still executing"); sleep.next_poll().await; ret = (f)(); } ret } - -pub async fn wait_for_with_cancel( - mut f: F, - sleep: &mut impl Sleep, - should_cancel: impl Fn(Duration) -> bool, -) -> SqlResult -where - F: FnMut(bool) -> SqlResult, -{ - let mut ret = (f)(should_cancel(Duration::ZERO)); - - let time = Instant::now(); - - // Wait for operation to finish, using polling method - while matches!(ret, SqlResult::StillExecuting) { - sleep.next_poll().await; - ret = (f)(should_cancel(time.elapsed())); - } - ret -} From e5fad680293a466df8e74a8daf8fec292ab972b6 Mon Sep 17 00:00:00 2001 From: Matthew Gapp <61894094+matthewgapp@users.noreply.github.com> Date: Sat, 13 Jul 2024 12:55:38 -0700 Subject: [PATCH 4/7] wip --- odbc-api/src/sleep.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/odbc-api/src/sleep.rs b/odbc-api/src/sleep.rs index e203d10b..866f0472 100644 --- a/odbc-api/src/sleep.rs +++ b/odbc-api/src/sleep.rs @@ -1,9 +1,4 @@ -use std::{ - future::Future, - time::{Duration, Instant}, -}; - -use log::info; +use std::future::Future; use crate::handles::SqlResult; @@ -38,7 +33,6 @@ where let mut ret = (f)(); // Wait for operation to finish, using polling method while matches!(ret, SqlResult::StillExecuting) { - info!("Still executing"); sleep.next_poll().await; ret = (f)(); } From 3c1ccefccc3a7efa097afed9b4a996df47faf274 Mon Sep 17 00:00:00 2001 From: Matthew Gapp <61894094+matthewgapp@users.noreply.github.com> Date: Sat, 13 Jul 2024 13:11:24 -0700 Subject: [PATCH 5/7] wip --- odbc-api/src/result_set_metadata.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/odbc-api/src/result_set_metadata.rs b/odbc-api/src/result_set_metadata.rs index 5d876b51..10878aa4 100644 --- a/odbc-api/src/result_set_metadata.rs +++ b/odbc-api/src/result_set_metadata.rs @@ -15,6 +15,8 @@ use crate::{ /// 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. /// From a20309759e83ebdfbeac5c115b24b97917d6b5fd Mon Sep 17 00:00:00 2001 From: Matthew Gapp <61894094+matthewgapp@users.noreply.github.com> Date: Sat, 13 Jul 2024 13:15:51 -0700 Subject: [PATCH 6/7] wip --- odbc-api/src/execute.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odbc-api/src/execute.rs b/odbc-api/src/execute.rs index b621bc96..270a5bae 100644 --- a/odbc-api/src/execute.rs +++ b/odbc-api/src/execute.rs @@ -1,4 +1,4 @@ -use std::{intrinsics::transmute, time::Duration}; +use std::intrinsics::transmute; use crate::{ handles::{AsStatementRef, SqlText, Statement}, From 55ac3d408f831baae86e63946962255c5e6077cd Mon Sep 17 00:00:00 2001 From: Matthew Gapp <61894094+matthewgapp@users.noreply.github.com> Date: Sat, 13 Jul 2024 13:39:25 -0700 Subject: [PATCH 7/7] wip --- Cargo.lock | 24 ------------------------ odbc-api/Cargo.toml | 2 -- odbc-api/src/sleep.rs | 3 +++ 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 275f3815..d2877431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,17 +144,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "async-trait" -version = "0.1.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atoi" version = "2.0.0" @@ -1117,7 +1106,6 @@ name = "odbc-api" version = "8.1.1" dependencies = [ "anyhow", - "async-trait", "atoi", "criterion", "csv", @@ -1131,7 +1119,6 @@ dependencies = [ "test-case", "thiserror", "tokio", - "trait-variant", "widestring", "winit", ] @@ -1647,17 +1634,6 @@ version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -[[package]] -name = "trait-variant" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/odbc-api/Cargo.toml b/odbc-api/Cargo.toml index 594173d0..f26db5af 100644 --- a/odbc-api/Cargo.toml +++ b/odbc-api/Cargo.toml @@ -77,8 +77,6 @@ log = "0.4.22" widestring = "1.1.0" atoi = "2.0.0" odbc-api-derive ={ version = "8.1.1", path = "../derive", optional = true} -trait-variant = "0.1.2" -async-trait = "0.1.81" [target.'cfg(windows)'.dependencies] # We use winit to display dialogs prompting for connection strings. We can deactivate default 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)(); }