Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
357 changes: 326 additions & 31 deletions odbc/src/api/connection.rs

Large diffs are not rendered by default.

101 changes: 61 additions & 40 deletions odbc/src/api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ pub enum OdbcError {
location: Location,
},

#[snafu(display("Invalid catalog name: {name}"))]
InvalidCatalogName {
name: String,
#[snafu(implicit)]
location: Location,
},

#[snafu(display("Invalid cursor state: no result set associated with the statement"))]
InvalidCursorState {
#[snafu(implicit)]
Expand Down Expand Up @@ -323,7 +330,7 @@ pub enum OdbcError {

#[snafu(display("Received core protobuf error"))]
CoreError {
source: CoreProtobufError,
source: Box<CoreProtobufError>,
#[snafu(implicit)]
location: Location,
},
Expand Down Expand Up @@ -427,6 +434,7 @@ impl OdbcError {
OdbcError::AttributeCannotBeSetNow { .. } => SqlState::AttributeCannotBeSetNow,
OdbcError::InvalidParameterNumber { .. } => SqlState::InvalidDescriptorIndex,
OdbcError::StatementNotExecuted { .. } => SqlState::FunctionSequenceError,
OdbcError::InvalidCatalogName { .. } => SqlState::InvalidCatalogName,
OdbcError::InvalidCursorState { .. } => SqlState::InvalidCursorState,
OdbcError::DataNotFetched { .. } => SqlState::FunctionSequenceError,
OdbcError::NoMoreData { .. } => SqlState::NoDataFound,
Expand Down Expand Up @@ -469,46 +477,56 @@ impl OdbcError {
OdbcError::TextConversionFromUtf8 { .. } => SqlState::StringDataRightTruncated,
OdbcError::TextConversionFromUtf16 { .. } => SqlState::StringDataRightTruncated,
OdbcError::JsonBinding { .. } => SqlState::GeneralError,
OdbcError::CoreError {
source: CoreProtobufError::Application { error, message, .. },
..
} => match error.as_ref() {
ErrorType::AuthError(_) => SqlState::InvalidAuthorizationSpecification,
ErrorType::GenericError(_) => {
if message.contains("SQL compilation error") {
SqlState::SyntaxErrorOrAccessRuleViolation
} else {
SqlState::GeneralError
}
}
ErrorType::InvalidParameterValue(ProtoInvalidParameterValue {
parameter, ..
}) => {
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
SqlState::InvalidAuthorizationSpecification
} else {
SqlState::InvalidConnectionStringAttribute
}
}
ErrorType::MissingParameter(ProtoMissingParameter { parameter }) => {
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
SqlState::InvalidAuthorizationSpecification
} else {
SqlState::InvalidConnectionStringAttribute
OdbcError::CoreError { source, .. } => match source.as_ref() {
CoreProtobufError::Transport { .. } => SqlState::ClientUnableToEstablishConnection,
CoreProtobufError::Application {
error,
message,
sql_state,
..
} => {
// Prefer the ANSI SQL state forwarded from the server when present.
if let Some(state) = sql_state
&& let Ok(parsed) = state.parse::<SqlState>()
{
return parsed;
}
}
ErrorType::InternalError(_) => {
if message.contains("SQL compilation error") {
SqlState::SyntaxErrorOrAccessRuleViolation
} else {
SqlState::GeneralError
match error.as_ref() {
ErrorType::AuthError(_) => SqlState::InvalidAuthorizationSpecification,
ErrorType::GenericError(_) => {
if message.contains("SQL compilation error") {
SqlState::SyntaxErrorOrAccessRuleViolation
} else {
SqlState::GeneralError
}
}
ErrorType::InvalidParameterValue(ProtoInvalidParameterValue {
parameter,
..
}) => {
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
SqlState::InvalidAuthorizationSpecification
} else {
SqlState::InvalidConnectionStringAttribute
}
}
ErrorType::MissingParameter(ProtoMissingParameter { parameter }) => {
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
SqlState::InvalidAuthorizationSpecification
} else {
SqlState::InvalidConnectionStringAttribute
}
}
ErrorType::InternalError(_) => {
if message.contains("SQL compilation error") {
SqlState::SyntaxErrorOrAccessRuleViolation
} else {
SqlState::GeneralError
}
}
ErrorType::LoginError(_) => SqlState::InvalidAuthorizationSpecification,
}
}
ErrorType::LoginError(_) => SqlState::InvalidAuthorizationSpecification,
},
OdbcError::CoreError { source, .. } => match source {
CoreProtobufError::Transport { .. } => SqlState::ClientUnableToEstablishConnection,
CoreProtobufError::Application { .. } => SqlState::GeneralError,
},
OdbcError::ProtoRequiredFieldMissing { .. } => SqlState::GeneralError,
OdbcError::ArrowArrayStreamReaderCreation { .. } => SqlState::GeneralError,
Expand All @@ -520,7 +538,7 @@ impl OdbcError {

pub fn to_native_error(&self) -> sql::Integer {
match self {
OdbcError::CoreError { source, .. } => match source {
OdbcError::CoreError { source, .. } => match source.as_ref() {
CoreProtobufError::Application { error, .. } => match error.as_ref() {
ErrorType::LoginError(login_error) => login_error.code,
_ => 0,
Expand All @@ -546,12 +564,13 @@ impl OdbcError {
message: driver_exception.message,
status_code: driver_exception.status_code,
error_trace: driver_exception.error_trace,
sql_state: driver_exception.sql_state,
location,
},
ProtoError::Transport(message) => CoreProtobufError::Transport { message, location },
};
OdbcError::CoreError {
source: core_error,
source: Box::new(core_error),
location,
}
}
Expand All @@ -572,6 +591,8 @@ pub enum CoreProtobufError {
message: String,
status_code: i32,
error_trace: Vec<ErrorTraceEntry>,
/// ANSI SQL state forwarded from the server response, if present.
sql_state: Option<String>,
location: Location,
},
#[snafu(display("Transport error: {message}"))]
Expand Down
17 changes: 16 additions & 1 deletion odbc/src/api/handle_allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub fn alloc_connection() -> OdbcResult<*mut Connection> {
diagnostic_info: DiagnosticInfo::default(),
pre_connection_attrs: Default::default(),
numeric_settings: Default::default(),
access_mode: 0, // SQL_MODE_READ_WRITE
quiet_mode: std::ptr::null_mut(), // no window handle
packet_size: 0, // driver-defined
child_statements: vec![],
txn_active: false,
cached_autocommit: true,
});
Ok(Box::into_raw(dbc))
}
Expand Down Expand Up @@ -74,7 +80,15 @@ pub fn alloc_statement(input_handle: sql::Handle) -> OdbcResult<*mut Statement<'
used_extended_fetch: false,
last_query_id: None,
});
Ok(Box::into_raw(stmt))
let stmt_ptr = Box::into_raw(stmt);
// Safety: stmt_ptr is valid — we just allocated it.
unsafe {
(*stmt_ptr)
.conn
.child_statements
.push(stmt_ptr as sql::Handle)
};
Ok(stmt_ptr)
}
ConnectionState::Disconnected => {
tracing::error!("Cannot allocate statement: connection is disconnected");
Expand Down Expand Up @@ -118,6 +132,7 @@ pub fn free_statement(handle: sql::Handle) -> OdbcResult<()> {

tracing::info!("Freeing statement handle");
let stmt = unsafe { Box::from_raw(handle as *mut Statement) };
stmt.conn.child_statements.retain(|&h| h != handle);
global().context(OdbcRuntimeSnafu)?.block_on(async |c| {
c.statement_release(StatementReleaseRequest {
stmt_handle: Some(stmt.stmt_handle),
Expand Down
6 changes: 6 additions & 0 deletions odbc/src/api/sql_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ pub enum SqlState {
/// 2H000 - Invalid collation name
InvalidCollationName,

// Invalid catalog name class (3D)
/// 3D000 - Invalid catalog name
InvalidCatalogName,

// Syntax error or access rule violation class (42)
/// 42000 - Syntax error or access rule violation
SyntaxErrorOrAccessRuleViolation,
Expand Down Expand Up @@ -368,6 +372,7 @@ impl SqlState {
SqlState::ReadingSqlDataNotPermitted => "2F004",
SqlState::FunctionExecutedNoReturnStatement => "2F005",
SqlState::InvalidCollationName => "2H000",
SqlState::InvalidCatalogName => "3D000",
SqlState::SyntaxErrorOrAccessRuleViolation => "42000",
SqlState::BaseTableOrViewAlreadyExists => "42S01",
SqlState::BaseTableOrViewNotFound => "42S02",
Expand Down Expand Up @@ -542,6 +547,7 @@ impl FromStr for SqlState {
"2F004" => SqlState::ReadingSqlDataNotPermitted,
"2F005" => SqlState::FunctionExecutedNoReturnStatement,
"2H000" => SqlState::InvalidCollationName,
"3D000" => SqlState::InvalidCatalogName,
"42000" => SqlState::SyntaxErrorOrAccessRuleViolation,
"42S01" => SqlState::BaseTableOrViewAlreadyExists,
"42S02" => SqlState::BaseTableOrViewNotFound,
Expand Down
6 changes: 6 additions & 0 deletions odbc/src/api/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ fn exec_direct_impl(statement_handle: sql::Handle, statement_text: &str) -> Odbc
update_numeric_settings(conn_handle, &mut stmt.conn.numeric_settings)?;
set_state(stmt, create_execute_state(response, false)?);
stmt.last_query_id = query_id.filter(|s| !s.is_empty());
if !stmt.conn.cached_autocommit {
stmt.conn.txn_active = true;
}
Ok(())
}
ConnectionState::Disconnected => {
Expand Down Expand Up @@ -253,6 +256,9 @@ pub fn execute(statement_handle: sql::Handle) -> OdbcResult<()> {

set_state(stmt, execute_state);
stmt.last_query_id = query_id.filter(|s| !s.is_empty());
if !stmt.conn.cached_autocommit {
stmt.conn.txn_active = true;
}
Ok(())
}
ConnectionState::Disconnected => {
Expand Down
39 changes: 39 additions & 0 deletions odbc/src/api/types/odbc_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,26 @@ const SQL_SF_CONN_ATTR_BASE: i32 = 0x4000 + 0x53;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConnectionAttribute {
// Standard ODBC attributes (from sql.h / sqlext.h)
/// SQL_ATTR_ACCESS_MODE (101)
AccessMode,
/// SQL_ATTR_AUTOCOMMIT (102)
Autocommit,
/// SQL_ATTR_LOGIN_TIMEOUT (103)
LoginTimeout,
/// SQL_ATTR_TXN_ISOLATION (108)
TxnIsolation,
/// SQL_ATTR_CURRENT_CATALOG (109)
CurrentCatalog,
/// SQL_ATTR_QUIET_MODE (111)
QuietMode,
/// SQL_ATTR_PACKET_SIZE (112)
PacketSize,
/// SQL_ATTR_CONNECTION_TIMEOUT (113)
ConnectionTimeout,
/// SQL_ATTR_CONNECTION_DEAD (1209) — read-only
ConnectionDead,
/// SQL_ATTR_AUTO_IPD (10001) — read-only
AutoIpd,

// Custom Snowflake attributes (matching sf_odbc.h)
/// SQL_SF_CONN_ATTR_PRIV_KEY — EVP_PKEY pointer (not supported in new driver)
Expand All @@ -50,10 +62,16 @@ impl ConnectionAttribute {
/// Returns `None` for unrecognized attributes.
pub fn from_raw(value: i32) -> Option<Self> {
match value {
101 => Some(Self::AccessMode),
102 => Some(Self::Autocommit),
103 => Some(Self::LoginTimeout),
108 => Some(Self::TxnIsolation),
109 => Some(Self::CurrentCatalog),
111 => Some(Self::QuietMode),
112 => Some(Self::PacketSize),
113 => Some(Self::ConnectionTimeout),
1209 => Some(Self::ConnectionDead),
10001 => Some(Self::AutoIpd),
x if x == SQL_SF_CONN_ATTR_BASE + 1 => Some(Self::PrivKey),
x if x == SQL_SF_CONN_ATTR_BASE + 2 => Some(Self::Application),
x if x == SQL_SF_CONN_ATTR_BASE + 3 => Some(Self::PrivKeyContent),
Expand All @@ -71,10 +89,16 @@ impl ConnectionAttribute {
/// Convert back to the raw ODBC attribute ID.
pub fn as_raw(&self) -> i32 {
match self {
Self::AccessMode => 101,
Self::Autocommit => 102,
Self::LoginTimeout => 103,
Self::TxnIsolation => 108,
Self::CurrentCatalog => 109,
Self::QuietMode => 111,
Self::PacketSize => 112,
Self::ConnectionTimeout => 113,
Self::ConnectionDead => 1209,
Self::AutoIpd => 10001,
Self::PrivKey => SQL_SF_CONN_ATTR_BASE + 1,
Self::Application => SQL_SF_CONN_ATTR_BASE + 2,
Self::PrivKeyContent => SQL_SF_CONN_ATTR_BASE + 3,
Expand Down Expand Up @@ -712,6 +736,21 @@ pub struct Connection {
/// Attributes set via SQLSetConnectAttr before the connection is established
pub pre_connection_attrs: PreConnectionAttributes,
pub numeric_settings: NumericSettings,
/// SQL_ATTR_ACCESS_MODE — advisory only (default SQL_MODE_READ_WRITE = 0)
pub access_mode: sql::UInteger,
/// SQL_ATTR_QUIET_MODE — window handle pointer (default null)
pub quiet_mode: sql::Pointer,
/// SQL_ATTR_PACKET_SIZE — pre-connect only (default 0 = driver-defined)
pub packet_size: sql::UInteger,
/// Raw handles of all child statements allocated on this connection.
/// Used to check for open cursors (24000) before switching catalog.
pub child_statements: Vec<sql::Handle>,
/// Whether a transaction is currently active (autocommit OFF + SQL executed).
/// Used to return HY011 when setting SQL_ATTR_TXN_ISOLATION.
pub txn_active: bool,
/// Cached local autocommit state. Defaults to true (ON).
/// Updated when SQL_ATTR_AUTOCOMMIT is set, used to track txn_active.
pub cached_autocommit: bool,
}

#[derive(Debug, Clone)]
Expand Down
18 changes: 16 additions & 2 deletions odbc/src/c_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,21 @@ pub unsafe extern "C" fn SQLSetConnectAttr(
string_length: sql::Integer,
) -> sql::RetCode {
api::diagnostic::clear_diag_info(sql::HandleType::Dbc, connection_handle);
let mut warnings = vec![];
let result = api::connection::set_connect_attr::<Narrow>(
connection_handle,
attribute,
value,
string_length,
&mut warnings,
);
api::diagnostic::set_diag_info_from_warnings(
sql::HandleType::Dbc,
connection_handle,
&warnings,
);
api::diagnostic::set_diag_info_from_result(sql::HandleType::Dbc, connection_handle, &result);
result.to_sql_code()
result.to_sql_code_with_warnings(&warnings)
}

/// # Safety
Expand All @@ -266,14 +273,21 @@ pub unsafe extern "C" fn SQLSetConnectAttrW(
string_length: sql::Integer,
) -> sql::RetCode {
api::diagnostic::clear_diag_info(sql::HandleType::Dbc, connection_handle);
let mut warnings = vec![];
let result = api::connection::set_connect_attr::<Wide>(
connection_handle,
attribute,
value,
string_length,
&mut warnings,
);
api::diagnostic::set_diag_info_from_warnings(
sql::HandleType::Dbc,
connection_handle,
&warnings,
);
api::diagnostic::set_diag_info_from_result(sql::HandleType::Dbc, connection_handle, &result);
result.to_sql_code()
result.to_sql_code_with_warnings(&warnings)
}

/// # Safety
Expand Down
1 change: 1 addition & 0 deletions odbc_tests/tests/odbc-api/connecting/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ add_odbc_test(odbc_api_alloc_handle_tests alloc_handle_tests.cpp)
add_odbc_test(odbc_api_connect_tests connect_tests.cpp)
add_odbc_test(odbc_api_driver_connect_tests driver_connect_tests.cpp)
add_odbc_test(odbc_api_browse_connect_tests browse_connect_tests.cpp)
add_odbc_test(odbc_api_conn_attr_tests conn_attr_tests.cpp)
Loading
Loading