Skip to content

Commit 90c9a05

Browse files
Connection Session & Database Attributes
1 parent d1fd932 commit 90c9a05

File tree

8 files changed

+706
-76
lines changed

8 files changed

+706
-76
lines changed

odbc/src/api/connection.rs

Lines changed: 300 additions & 34 deletions
Large diffs are not rendered by default.

odbc/src/api/error.rs

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ pub enum OdbcError {
323323

324324
#[snafu(display("Received core protobuf error"))]
325325
CoreError {
326-
source: CoreProtobufError,
326+
source: Box<CoreProtobufError>,
327327
#[snafu(implicit)]
328328
location: Location,
329329
},
@@ -469,46 +469,56 @@ impl OdbcError {
469469
OdbcError::TextConversionFromUtf8 { .. } => SqlState::StringDataRightTruncated,
470470
OdbcError::TextConversionFromUtf16 { .. } => SqlState::StringDataRightTruncated,
471471
OdbcError::JsonBinding { .. } => SqlState::GeneralError,
472-
OdbcError::CoreError {
473-
source: CoreProtobufError::Application { error, message, .. },
474-
..
475-
} => match error.as_ref() {
476-
ErrorType::AuthError(_) => SqlState::InvalidAuthorizationSpecification,
477-
ErrorType::GenericError(_) => {
478-
if message.contains("SQL compilation error") {
479-
SqlState::SyntaxErrorOrAccessRuleViolation
480-
} else {
481-
SqlState::GeneralError
482-
}
483-
}
484-
ErrorType::InvalidParameterValue(ProtoInvalidParameterValue {
485-
parameter, ..
486-
}) => {
487-
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
488-
SqlState::InvalidAuthorizationSpecification
489-
} else {
490-
SqlState::InvalidConnectionStringAttribute
491-
}
492-
}
493-
ErrorType::MissingParameter(ProtoMissingParameter { parameter }) => {
494-
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
495-
SqlState::InvalidAuthorizationSpecification
496-
} else {
497-
SqlState::InvalidConnectionStringAttribute
472+
OdbcError::CoreError { source, .. } => match source.as_ref() {
473+
CoreProtobufError::Transport { .. } => SqlState::ClientUnableToEstablishConnection,
474+
CoreProtobufError::Application {
475+
error,
476+
message,
477+
sql_state,
478+
..
479+
} => {
480+
// Prefer the ANSI SQL state forwarded from the server when present.
481+
if let Some(state) = sql_state
482+
&& let Ok(parsed) = state.parse::<SqlState>()
483+
{
484+
return parsed;
498485
}
499-
}
500-
ErrorType::InternalError(_) => {
501-
if message.contains("SQL compilation error") {
502-
SqlState::SyntaxErrorOrAccessRuleViolation
503-
} else {
504-
SqlState::GeneralError
486+
match error.as_ref() {
487+
ErrorType::AuthError(_) => SqlState::InvalidAuthorizationSpecification,
488+
ErrorType::GenericError(_) => {
489+
if message.contains("SQL compilation error") {
490+
SqlState::SyntaxErrorOrAccessRuleViolation
491+
} else {
492+
SqlState::GeneralError
493+
}
494+
}
495+
ErrorType::InvalidParameterValue(ProtoInvalidParameterValue {
496+
parameter,
497+
..
498+
}) => {
499+
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
500+
SqlState::InvalidAuthorizationSpecification
501+
} else {
502+
SqlState::InvalidConnectionStringAttribute
503+
}
504+
}
505+
ErrorType::MissingParameter(ProtoMissingParameter { parameter }) => {
506+
if AUTHENTICATOR_PARAMETERS.contains(&parameter.to_uppercase()) {
507+
SqlState::InvalidAuthorizationSpecification
508+
} else {
509+
SqlState::InvalidConnectionStringAttribute
510+
}
511+
}
512+
ErrorType::InternalError(_) => {
513+
if message.contains("SQL compilation error") {
514+
SqlState::SyntaxErrorOrAccessRuleViolation
515+
} else {
516+
SqlState::GeneralError
517+
}
518+
}
519+
ErrorType::LoginError(_) => SqlState::InvalidAuthorizationSpecification,
505520
}
506521
}
507-
ErrorType::LoginError(_) => SqlState::InvalidAuthorizationSpecification,
508-
},
509-
OdbcError::CoreError { source, .. } => match source {
510-
CoreProtobufError::Transport { .. } => SqlState::ClientUnableToEstablishConnection,
511-
CoreProtobufError::Application { .. } => SqlState::GeneralError,
512522
},
513523
OdbcError::ProtoRequiredFieldMissing { .. } => SqlState::GeneralError,
514524
OdbcError::ArrowArrayStreamReaderCreation { .. } => SqlState::GeneralError,
@@ -520,7 +530,7 @@ impl OdbcError {
520530

521531
pub fn to_native_error(&self) -> sql::Integer {
522532
match self {
523-
OdbcError::CoreError { source, .. } => match source {
533+
OdbcError::CoreError { source, .. } => match source.as_ref() {
524534
CoreProtobufError::Application { error, .. } => match error.as_ref() {
525535
ErrorType::LoginError(login_error) => login_error.code,
526536
_ => 0,
@@ -546,12 +556,13 @@ impl OdbcError {
546556
message: driver_exception.message,
547557
status_code: driver_exception.status_code,
548558
error_trace: driver_exception.error_trace,
559+
sql_state: driver_exception.sql_state,
549560
location,
550561
},
551562
ProtoError::Transport(message) => CoreProtobufError::Transport { message, location },
552563
};
553564
OdbcError::CoreError {
554-
source: core_error,
565+
source: Box::new(core_error),
555566
location,
556567
}
557568
}
@@ -572,6 +583,8 @@ pub enum CoreProtobufError {
572583
message: String,
573584
status_code: i32,
574585
error_trace: Vec<ErrorTraceEntry>,
586+
/// ANSI SQL state forwarded from the server response, if present.
587+
sql_state: Option<String>,
575588
location: Location,
576589
},
577590
#[snafu(display("Transport error: {message}"))]

odbc/src/api/handle_allocation.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub fn alloc_connection() -> OdbcResult<*mut Connection> {
3535
diagnostic_info: DiagnosticInfo::default(),
3636
pre_connection_attrs: Default::default(),
3737
numeric_settings: Default::default(),
38+
access_mode: 0, // SQL_MODE_READ_WRITE
39+
quiet_mode: std::ptr::null_mut(), // no window handle
40+
packet_size: 0, // driver-defined
3841
});
3942
Ok(Box::into_raw(dbc))
4043
}

odbc/src/api/sql_state.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ pub enum SqlState {
165165
/// 2H000 - Invalid collation name
166166
InvalidCollationName,
167167

168+
// Invalid catalog name class (3D)
169+
/// 3D000 - Invalid catalog name
170+
InvalidCatalogName,
171+
168172
// Syntax error or access rule violation class (42)
169173
/// 42000 - Syntax error or access rule violation
170174
SyntaxErrorOrAccessRuleViolation,
@@ -368,6 +372,7 @@ impl SqlState {
368372
SqlState::ReadingSqlDataNotPermitted => "2F004",
369373
SqlState::FunctionExecutedNoReturnStatement => "2F005",
370374
SqlState::InvalidCollationName => "2H000",
375+
SqlState::InvalidCatalogName => "3D000",
371376
SqlState::SyntaxErrorOrAccessRuleViolation => "42000",
372377
SqlState::BaseTableOrViewAlreadyExists => "42S01",
373378
SqlState::BaseTableOrViewNotFound => "42S02",
@@ -542,6 +547,7 @@ impl FromStr for SqlState {
542547
"2F004" => SqlState::ReadingSqlDataNotPermitted,
543548
"2F005" => SqlState::FunctionExecutedNoReturnStatement,
544549
"2H000" => SqlState::InvalidCollationName,
550+
"3D000" => SqlState::InvalidCatalogName,
545551
"42000" => SqlState::SyntaxErrorOrAccessRuleViolation,
546552
"42S01" => SqlState::BaseTableOrViewAlreadyExists,
547553
"42S02" => SqlState::BaseTableOrViewNotFound,

odbc/src/api/types/odbc_types.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,26 @@ const SQL_SF_CONN_ATTR_BASE: i32 = 0x4000 + 0x53;
2323
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2424
pub enum ConnectionAttribute {
2525
// Standard ODBC attributes (from sql.h / sqlext.h)
26+
/// SQL_ATTR_ACCESS_MODE (101)
27+
AccessMode,
2628
/// SQL_ATTR_AUTOCOMMIT (102)
2729
Autocommit,
2830
/// SQL_ATTR_LOGIN_TIMEOUT (103)
2931
LoginTimeout,
32+
/// SQL_ATTR_TXN_ISOLATION (108)
33+
TxnIsolation,
3034
/// SQL_ATTR_CURRENT_CATALOG (109)
3135
CurrentCatalog,
36+
/// SQL_ATTR_QUIET_MODE (111)
37+
QuietMode,
38+
/// SQL_ATTR_PACKET_SIZE (112)
39+
PacketSize,
3240
/// SQL_ATTR_CONNECTION_TIMEOUT (113)
3341
ConnectionTimeout,
42+
/// SQL_ATTR_CONNECTION_DEAD (1209) — read-only
43+
ConnectionDead,
44+
/// SQL_ATTR_AUTO_IPD (10001) — read-only
45+
AutoIpd,
3446

3547
// Custom Snowflake attributes (matching sf_odbc.h)
3648
/// SQL_SF_CONN_ATTR_PRIV_KEY — EVP_PKEY pointer (not supported in new driver)
@@ -50,10 +62,16 @@ impl ConnectionAttribute {
5062
/// Returns `None` for unrecognized attributes.
5163
pub fn from_raw(value: i32) -> Option<Self> {
5264
match value {
65+
101 => Some(Self::AccessMode),
5366
102 => Some(Self::Autocommit),
5467
103 => Some(Self::LoginTimeout),
68+
108 => Some(Self::TxnIsolation),
5569
109 => Some(Self::CurrentCatalog),
70+
111 => Some(Self::QuietMode),
71+
112 => Some(Self::PacketSize),
5672
113 => Some(Self::ConnectionTimeout),
73+
1209 => Some(Self::ConnectionDead),
74+
10001 => Some(Self::AutoIpd),
5775
x if x == SQL_SF_CONN_ATTR_BASE + 1 => Some(Self::PrivKey),
5876
x if x == SQL_SF_CONN_ATTR_BASE + 2 => Some(Self::Application),
5977
x if x == SQL_SF_CONN_ATTR_BASE + 3 => Some(Self::PrivKeyContent),
@@ -71,10 +89,16 @@ impl ConnectionAttribute {
7189
/// Convert back to the raw ODBC attribute ID.
7290
pub fn as_raw(&self) -> i32 {
7391
match self {
92+
Self::AccessMode => 101,
7493
Self::Autocommit => 102,
7594
Self::LoginTimeout => 103,
95+
Self::TxnIsolation => 108,
7696
Self::CurrentCatalog => 109,
97+
Self::QuietMode => 111,
98+
Self::PacketSize => 112,
7799
Self::ConnectionTimeout => 113,
100+
Self::ConnectionDead => 1209,
101+
Self::AutoIpd => 10001,
78102
Self::PrivKey => SQL_SF_CONN_ATTR_BASE + 1,
79103
Self::Application => SQL_SF_CONN_ATTR_BASE + 2,
80104
Self::PrivKeyContent => SQL_SF_CONN_ATTR_BASE + 3,
@@ -712,6 +736,12 @@ pub struct Connection {
712736
/// Attributes set via SQLSetConnectAttr before the connection is established
713737
pub pre_connection_attrs: PreConnectionAttributes,
714738
pub numeric_settings: NumericSettings,
739+
/// SQL_ATTR_ACCESS_MODE — advisory only (default SQL_MODE_READ_WRITE = 0)
740+
pub access_mode: sql::UInteger,
741+
/// SQL_ATTR_QUIET_MODE — window handle pointer (default null)
742+
pub quiet_mode: sql::Pointer,
743+
/// SQL_ATTR_PACKET_SIZE — pre-connect only (default 0 = driver-defined)
744+
pub packet_size: sql::UInteger,
715745
}
716746

717747
#[derive(Debug, Clone)]

odbc/src/c_api.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,21 @@ pub unsafe extern "C" fn SQLSetConnectAttr(
246246
string_length: sql::Integer,
247247
) -> sql::RetCode {
248248
api::diagnostic::clear_diag_info(sql::HandleType::Dbc, connection_handle);
249+
let mut warnings = vec![];
249250
let result = api::connection::set_connect_attr::<Narrow>(
250251
connection_handle,
251252
attribute,
252253
value,
253254
string_length,
255+
&mut warnings,
256+
);
257+
api::diagnostic::set_diag_info_from_warnings(
258+
sql::HandleType::Dbc,
259+
connection_handle,
260+
&warnings,
254261
);
255262
api::diagnostic::set_diag_info_from_result(sql::HandleType::Dbc, connection_handle, &result);
256-
result.to_sql_code()
263+
result.to_sql_code_with_warnings(&warnings)
257264
}
258265

259266
/// # Safety
@@ -266,14 +273,21 @@ pub unsafe extern "C" fn SQLSetConnectAttrW(
266273
string_length: sql::Integer,
267274
) -> sql::RetCode {
268275
api::diagnostic::clear_diag_info(sql::HandleType::Dbc, connection_handle);
276+
let mut warnings = vec![];
269277
let result = api::connection::set_connect_attr::<Wide>(
270278
connection_handle,
271279
attribute,
272280
value,
273281
string_length,
282+
&mut warnings,
283+
);
284+
api::diagnostic::set_diag_info_from_warnings(
285+
sql::HandleType::Dbc,
286+
connection_handle,
287+
&warnings,
274288
);
275289
api::diagnostic::set_diag_info_from_result(sql::HandleType::Dbc, connection_handle, &result);
276-
result.to_sql_code()
290+
result.to_sql_code_with_warnings(&warnings)
277291
}
278292

279293
/// # Safety

odbc_tests/tests/odbc-api/connecting/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ add_odbc_test(odbc_api_alloc_handle_tests alloc_handle_tests.cpp)
44
add_odbc_test(odbc_api_connect_tests connect_tests.cpp)
55
add_odbc_test(odbc_api_driver_connect_tests driver_connect_tests.cpp)
66
add_odbc_test(odbc_api_browse_connect_tests browse_connect_tests.cpp)
7+
add_odbc_test(odbc_api_conn_attr_tests conn_attr_tests.cpp)

0 commit comments

Comments
 (0)