Skip to content

Commit 0ad3649

Browse files
committed
feat: Enhance ODBC connection string support
This commit adds support for standard ODBC connection strings in the `AnyKind` implementation, allowing for automatic detection of connection strings without the `odbc:` prefix. Additionally, comprehensive tests have been introduced to validate the parsing of various ODBC connection string formats.
1 parent ec0f1d6 commit 0ad3649

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

sqlx-core/src/any/kind.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,26 @@ impl FromStr for AnyKind {
6565
}
6666

6767
#[cfg(feature = "odbc")]
68-
_ if url.starts_with("odbc:") => {
68+
_ if url.starts_with("odbc:") || Self::is_odbc_connection_string(url) => {
6969
Ok(AnyKind::Odbc)
7070
}
7171

7272
#[cfg(not(feature = "odbc"))]
73-
_ if url.starts_with("odbc:") => {
73+
_ if url.starts_with("odbc:") || Self::is_odbc_connection_string(url) => {
7474
Err(Error::Configuration("database URL has the scheme of an ODBC database but the `odbc` feature is not enabled".into()))
7575
}
7676

7777
_ => Err(Error::Configuration(format!("unrecognized database url: {:?}", url).into()))
7878
}
7979
}
8080
}
81+
82+
impl AnyKind {
83+
fn is_odbc_connection_string(s: &str) -> bool {
84+
let s_upper = s.to_uppercase();
85+
s_upper.starts_with("DSN=")
86+
|| s_upper.starts_with("DRIVER=")
87+
|| s_upper.starts_with("FILEDSN=")
88+
|| (s_upper.contains("DRIVER=") && s_upper.contains(';'))
89+
}
90+
}

sqlx-core/src/odbc/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
11
//! ODBC database driver (via `odbc-api`).
2+
//!
3+
//! ## Connection Strings
4+
//!
5+
//! When using the `Any` connection type, SQLx accepts standard ODBC connection strings:
6+
//!
7+
//! ```text
8+
//! // DSN-based connection
9+
//! DSN=MyDataSource;UID=myuser;PWD=mypassword
10+
//!
11+
//! // Driver-based connection
12+
//! Driver={ODBC Driver 17 for SQL Server};Server=localhost;Database=test
13+
//!
14+
//! // File DSN
15+
//! FILEDSN=/path/to/myfile.dsn
16+
//! ```
17+
//!
18+
//! The `odbc:` URL scheme prefix is optional but still supported for backward compatibility:
19+
//!
20+
//! ```text
21+
//! odbc:DSN=MyDataSource
22+
//! ```
223
324
use crate::executor::Executor;
425

tests/any/odbc.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use sqlx_oldapi::{Column, Connection, Executor, Row, Statement};
66
async fn odbc_conn() -> anyhow::Result<AnyConnection> {
77
let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set for ODBC tests");
88

9-
// Ensure the URL starts with "odbc:"
9+
// The "odbc:" prefix is now optional - standard ODBC connection strings
10+
// like "DSN=mydsn" or "Driver={SQL Server};..." are automatically detected
1011
let url = if !url.starts_with("odbc:") {
1112
format!("odbc:{}", url)
1213
} else {
@@ -340,3 +341,59 @@ async fn it_matches_any_kind_odbc() -> anyhow::Result<()> {
340341
conn.close().await?;
341342
Ok(())
342343
}
344+
345+
#[cfg(feature = "odbc")]
346+
#[sqlx_macros::test]
347+
async fn it_accepts_standard_odbc_connection_strings() -> anyhow::Result<()> {
348+
use sqlx_oldapi::any::AnyKind;
349+
use std::str::FromStr;
350+
351+
// Test various standard ODBC connection string formats
352+
let test_cases = vec![
353+
"DSN=mydsn",
354+
"DSN=mydsn;UID=user;PWD=pass",
355+
"Driver={SQL Server};Server=localhost;Database=test",
356+
"Driver={ODBC Driver 17 for SQL Server};Server=localhost;Database=test",
357+
"FILEDSN=myfile.dsn",
358+
"odbc:DSN=mydsn", // Still support the odbc: prefix
359+
"odbc:Driver={SQL Server};Server=localhost",
360+
];
361+
362+
for conn_str in test_cases {
363+
let kind_result = AnyKind::from_str(conn_str);
364+
365+
// If ODBC feature is enabled, these should parse as ODBC
366+
match kind_result {
367+
Ok(kind) => assert_eq!(
368+
kind,
369+
AnyKind::Odbc,
370+
"Failed to identify '{}' as ODBC",
371+
conn_str
372+
),
373+
Err(e) => panic!("Failed to parse '{}' as ODBC: {}", conn_str, e),
374+
}
375+
}
376+
377+
// Test non-ODBC connection strings don't match
378+
let non_odbc_cases = vec![
379+
"postgres://localhost/db",
380+
"mysql://localhost/db",
381+
"sqlite:memory:",
382+
"random string without equals",
383+
];
384+
385+
for conn_str in non_odbc_cases {
386+
let kind_result = AnyKind::from_str(conn_str);
387+
match kind_result {
388+
Ok(kind) => assert_ne!(
389+
kind,
390+
AnyKind::Odbc,
391+
"Incorrectly identified '{}' as ODBC",
392+
conn_str
393+
),
394+
Err(_) => {} // Expected for unrecognized formats
395+
}
396+
}
397+
398+
Ok(())
399+
}

0 commit comments

Comments
 (0)