Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async metadata support #577

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions odbc-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion odbc-api/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,12 @@ impl<'c> Connection<'c> {
sleep: impl Sleep,
) -> Result<Option<CursorPolling<StatementImpl<'_>>>, 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)?;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flyby: shouldn't this implementation set the statement to be async?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch: Fixed in 8.1.2

Ok(stmt)
})
};
execute_with_parameters_polling(lazy_statement, Some(&query), params, sleep).await
}

Expand Down
4 changes: 3 additions & 1 deletion odbc-api/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -314,6 +315,7 @@ where
}

impl<S> ResultSetMetadata for CursorImpl<S> where S: AsStatementRef {}
impl<S> AsyncResultSetMetadata for CursorPolling<S> where S: AsStatementRef {}

impl<S> Cursor for CursorImpl<S>
where
Expand Down
2 changes: 1 addition & 1 deletion odbc-api/src/execute.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::intrinsics::transmute;
use std::{intrinsics::transmute, time::Duration};

use crate::{
handles::{AsStatementRef, SqlText, Statement},
Expand Down
79 changes: 76 additions & 3 deletions odbc-api/src/handles/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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(
Expand Down Expand Up @@ -374,13 +410,16 @@ 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")
}

/// 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<i16> {
let mut out: i16 = 0;
unsafe { SQLNumResultCols(self.as_sys(), &mut out) }
Expand All @@ -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<u16> {
let mut out: i16 = 0;
unsafe { SQLNumParams(self.as_sys(), &mut out) }
Expand Down Expand Up @@ -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<bool> {
unsafe { self.numeric_col_attribute(Desc::Unsigned, column_number) }.map(|out| match out {
0 => false,
Expand All @@ -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<SqlDataType> {
unsafe { self.numeric_col_attribute(Desc::Type, column_number) }.map(|ret| {
SqlDataType(ret.try_into().expect(
Expand All @@ -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<SqlDataType> {
unsafe { self.numeric_col_attribute(Desc::ConciseType, column_number) }.map(|ret| {
SqlDataType(ret.try_into().expect(
Expand All @@ -639,13 +686,17 @@ 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<isize> {
unsafe { self.numeric_col_attribute(Desc::OctetLength, column_number) }
}

/// 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<isize> {
unsafe { self.numeric_col_attribute(Desc::DisplaySize, column_number) }
}
Expand All @@ -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<isize> {
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<Len> {
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<SqlChar>) -> SqlResult<()> {
// String length in bytes, not characters. Terminating zero is excluded.
let mut string_length_in_bytes: i16 = 0;
Expand Down Expand Up @@ -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<ParameterDescription> {
let mut data_type = SqlDataType::UNKNOWN_TYPE;
let mut parameter_size = 0;
Expand Down Expand Up @@ -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<Option<Pointer>> {
unsafe {
let mut param_id: Pointer = null_mut();
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -903,6 +972,8 @@ pub trait Statement: AsHandle {
///
/// <https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-api/sqlrowcount>
/// <https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlrowcount-function>
///
/// Note: This function can be called asynchronously per Microsoft docs.
fn row_count(&self) -> SqlResult<isize> {
let mut ret = 0isize;
unsafe {
Expand Down Expand Up @@ -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") }
}
Expand Down
2 changes: 1 addition & 1 deletion odbc-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
1 change: 1 addition & 0 deletions odbc-api/src/prepared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down
Loading