From f143ea0c24a57ccf5d29ae7a3541e03a4c6492d6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 22:32:28 +0000 Subject: [PATCH 01/10] feat: Add SSRP support for MSSQL named instances Co-authored-by: contact --- sqlx-core/src/mssql/connection/mod.rs | 1 + sqlx-core/src/mssql/connection/ssrp.rs | 160 +++++++++++++++++++++++ sqlx-core/src/mssql/connection/stream.rs | 8 +- sqlx-core/src/mssql/options/mod.rs | 8 +- sqlx-core/src/mssql/options/parse.rs | 5 +- sqlx-rt/src/rt_async_std.rs | 4 +- sqlx-rt/src/rt_tokio.rs | 4 +- 7 files changed, 182 insertions(+), 8 deletions(-) create mode 100644 sqlx-core/src/mssql/connection/ssrp.rs diff --git a/sqlx-core/src/mssql/connection/mod.rs b/sqlx-core/src/mssql/connection/mod.rs index a3aac85f88..4975fafc99 100644 --- a/sqlx-core/src/mssql/connection/mod.rs +++ b/sqlx-core/src/mssql/connection/mod.rs @@ -14,6 +14,7 @@ use std::sync::Arc; mod establish; mod executor; mod prepare; +mod ssrp; mod stream; mod tls_prelogin_stream_wrapper; diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs new file mode 100644 index 0000000000..8888bae43e --- /dev/null +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -0,0 +1,160 @@ +use crate::error::Error; +use sqlx_rt::{timeout, UdpSocket}; +use std::collections::HashMap; +use std::time::Duration; + +const SSRP_PORT: u16 = 1434; +const CLNT_UCAST_INST: u8 = 0x04; +const SVR_RESP: u8 = 0x05; +const SSRP_TIMEOUT: Duration = Duration::from_secs(1); + +pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Result { + let mut request = Vec::with_capacity(1 + instance.len() + 1); + request.push(CLNT_UCAST_INST); + request.extend_from_slice(instance.as_bytes()); + request.push(0); + + let socket = UdpSocket::bind("0.0.0.0:0").await.map_err(|e| { + err_protocol!("failed to bind UDP socket for SSRP: {}", e) + })?; + + socket + .send_to(&request, (server, SSRP_PORT)) + .await + .map_err(|e| { + err_protocol!("failed to send SSRP request to {}:{}: {}", server, SSRP_PORT, e) + })?; + + let mut buffer = [0u8; 1024]; + let bytes_read = timeout(SSRP_TIMEOUT, socket.recv(&mut buffer)) + .await + .map_err(|_| { + err_protocol!( + "SSRP request to {} for instance {} timed out after {:?}", + server, + instance, + SSRP_TIMEOUT + ) + })? + .map_err(|e| { + err_protocol!( + "failed to receive SSRP response from {} for instance {}: {}", + server, + instance, + e + ) + })?; + + if bytes_read < 3 { + return Err(err_protocol!( + "SSRP response too short: {} bytes", + bytes_read + )); + } + + if buffer[0] != SVR_RESP { + return Err(err_protocol!( + "invalid SSRP response type: expected 0x05, got 0x{:02x}", + buffer[0] + )); + } + + let response_size = u16::from_le_bytes([buffer[1], buffer[2]]) as usize; + if response_size + 3 > bytes_read { + return Err(err_protocol!( + "SSRP response size mismatch: expected {} bytes, got {}", + response_size + 3, + bytes_read + )); + } + + let response_data = String::from_utf8(buffer[3..(3 + response_size)].to_vec()) + .map_err(|e| err_protocol!("SSRP response is not valid UTF-8: {}", e))?; + + parse_ssrp_response(&response_data, instance) +} + +fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { + let instances: Vec<&str> = data.split(";;").collect(); + + for instance_data in instances { + if instance_data.is_empty() { + continue; + } + + let tokens: Vec<&str> = instance_data.split(';').collect(); + let mut properties: HashMap<&str, &str> = HashMap::new(); + + let mut i = 0; + while i + 1 < tokens.len() { + let key = tokens[i]; + let value = tokens[i + 1]; + properties.insert(key, value); + i += 2; + } + + if let Some(name) = properties.get("InstanceName") { + if name.eq_ignore_ascii_case(instance_name) { + if let Some(tcp_port_str) = properties.get("tcp") { + return tcp_port_str.parse::().map_err(|e| { + err_protocol!( + "invalid TCP port '{}' in SSRP response: {}", + tcp_port_str, + e + ) + }); + } else { + return Err(err_protocol!( + "instance '{}' found but no TCP port available", + instance_name + )); + } + } + } + } + + Err(err_protocol!( + "instance '{}' not found in SSRP response", + instance_name + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_ssrp_response_single_instance() { + let data = "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;tcp;1433;;"; + let port = parse_ssrp_response(data, "SQLEXPRESS").unwrap(); + assert_eq!(port, 1433); + } + + #[test] + fn test_parse_ssrp_response_multiple_instances() { + let data = "ServerName;SRV1;InstanceName;INST1;IsClustered;No;Version;15.0.2000.5;tcp;1433;;ServerName;SRV1;InstanceName;INST2;IsClustered;No;Version;16.0.1000.6;tcp;1434;np;\\\\SRV1\\pipe\\MSSQL$INST2\\sql\\query;;"; + let port = parse_ssrp_response(data, "INST2").unwrap(); + assert_eq!(port, 1434); + } + + #[test] + fn test_parse_ssrp_response_case_insensitive() { + let data = "ServerName;MYSERVER;InstanceName;SQLExpress;IsClustered;No;Version;15.0.2000.5;tcp;1433;;"; + let port = parse_ssrp_response(data, "sqlexpress").unwrap(); + assert_eq!(port, 1433); + } + + #[test] + fn test_parse_ssrp_response_instance_not_found() { + let data = "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;tcp;1433;;"; + let result = parse_ssrp_response(data, "NOTFOUND"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_ssrp_response_no_tcp_port() { + let data = "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;;"; + let result = parse_ssrp_response(data, "SQLEXPRESS"); + assert!(result.is_err()); + } +} diff --git a/sqlx-core/src/mssql/connection/stream.rs b/sqlx-core/src/mssql/connection/stream.rs index b19c26578d..3e637fca7c 100644 --- a/sqlx-core/src/mssql/connection/stream.rs +++ b/sqlx-core/src/mssql/connection/stream.rs @@ -51,7 +51,13 @@ pub(crate) struct MssqlStream { impl MssqlStream { pub(super) async fn connect(options: &MssqlConnectOptions) -> Result { - let tcp_stream = TcpStream::connect((&*options.host, options.port)).await?; + let port = if let Some(ref instance) = options.instance { + super::ssrp::resolve_instance_port(&options.host, instance).await? + } else { + options.port + }; + + let tcp_stream = TcpStream::connect((&*options.host, port)).await?; let wrapped_stream = TlsPreloginWrapper::new(tcp_stream); let inner = BufStream::new(MaybeTlsStream::Raw(wrapped_stream)); diff --git a/sqlx-core/src/mssql/options/mod.rs b/sqlx-core/src/mssql/options/mod.rs index 3e90e070e1..9dc72945a0 100644 --- a/sqlx-core/src/mssql/options/mod.rs +++ b/sqlx-core/src/mssql/options/mod.rs @@ -10,8 +10,14 @@ mod parse; /// /// Connection strings should be in the form: /// ```text -/// mssql://[username[:password]@]host/database[?instance=instance_name&packet_size=packet_size&client_program_version=client_program_version&client_pid=client_pid&hostname=hostname&app_name=app_name&server_name=server_name&client_interface_name=client_interface_name&language=language] +/// mssql://[username[:password]@]host[:port]/database[?param1=value1¶m2=value2...] /// ``` +/// +/// When connecting to a named instance, use the `instance` parameter: +/// ```text +/// mssql://user:pass@localhost/mydb?instance=SQLEXPRESS +/// ``` +/// The port will be automatically discovered using the SQL Server Resolution Protocol (SSRP). #[derive(Debug, Clone)] pub struct MssqlConnectOptions { pub(crate) host: String, diff --git a/sqlx-core/src/mssql/options/parse.rs b/sqlx-core/src/mssql/options/parse.rs index fb6d921c98..68f7214e2b 100644 --- a/sqlx-core/src/mssql/options/parse.rs +++ b/sqlx-core/src/mssql/options/parse.rs @@ -23,7 +23,7 @@ impl FromStr for MssqlConnectOptions { /// - `database`: The name of the database to connect to. /// /// Supported query parameters: - /// - `instance`: SQL Server named instance. + /// - `instance`: SQL Server named instance. When specified, the port is automatically discovered using the SQL Server Resolution Protocol (SSRP). /// - `encrypt`: Controls connection encryption: /// - `strict`: Requires encryption and validates the server certificate. /// - `mandatory` or `true` or `yes`: Requires encryption but doesn't validate the server certificate. @@ -41,9 +41,10 @@ impl FromStr for MssqlConnectOptions { /// - `client_interface_name`: Name of the client interface, sent to the server for logging purposes. /// - `language`: Sets the language for server messages. Affects date formats and system messages. /// - /// Example: + /// Examples: /// ```text /// mssql://user:pass@localhost:1433/mydb?encrypt=strict&app_name=MyApp&packet_size=4096 + /// mssql://user:pass@localhost/mydb?instance=SQLEXPRESS /// ``` fn from_str(s: &str) -> Result { let url: Url = s.parse().map_err(Error::config)?; diff --git a/sqlx-rt/src/rt_async_std.rs b/sqlx-rt/src/rt_async_std.rs index e8ccb49849..dfced0520d 100644 --- a/sqlx-rt/src/rt_async_std.rs +++ b/sqlx-rt/src/rt_async_std.rs @@ -1,8 +1,8 @@ pub use async_std::{ self, fs, future::timeout, io::prelude::ReadExt as AsyncReadExt, io::prelude::WriteExt as AsyncWriteExt, io::Read as AsyncRead, io::Write as AsyncWrite, - net::TcpStream, sync::Mutex as AsyncMutex, task::sleep, task::spawn, task::spawn_blocking, - task::yield_now, + net::TcpStream, net::UdpSocket, sync::Mutex as AsyncMutex, task::sleep, task::spawn, + task::spawn_blocking, task::yield_now, }; #[cfg(unix)] diff --git a/sqlx-rt/src/rt_tokio.rs b/sqlx-rt/src/rt_tokio.rs index 72b2cbb27b..615cbc2d19 100644 --- a/sqlx-rt/src/rt_tokio.rs +++ b/sqlx-rt/src/rt_tokio.rs @@ -1,7 +1,7 @@ pub use tokio::{ self, fs, io::AsyncRead, io::AsyncReadExt, io::AsyncWrite, io::AsyncWriteExt, io::ReadBuf, - net::TcpStream, runtime::Handle, sync::Mutex as AsyncMutex, task::spawn, task::yield_now, - time::sleep, time::timeout, + net::TcpStream, net::UdpSocket, runtime::Handle, sync::Mutex as AsyncMutex, task::spawn, + task::yield_now, time::sleep, time::timeout, }; #[cfg(unix)] From 6179f0928602ed2047b9e986c8b5ab91249575c9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 22:37:32 +0000 Subject: [PATCH 02/10] Add logging to MssqlStream and SSRP instance resolution Co-authored-by: contact --- sqlx-core/src/mssql/connection/ssrp.rs | 88 +++++++++++++++++++++++- sqlx-core/src/mssql/connection/stream.rs | 3 + 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs index 8888bae43e..e590effdb7 100644 --- a/sqlx-core/src/mssql/connection/ssrp.rs +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -9,19 +9,34 @@ const SVR_RESP: u8 = 0x05; const SSRP_TIMEOUT: Duration = Duration::from_secs(1); pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Result { + log::debug!( + "resolving SQL Server instance port for '{}' on server '{}'", + instance, + server + ); + let mut request = Vec::with_capacity(1 + instance.len() + 1); request.push(CLNT_UCAST_INST); request.extend_from_slice(instance.as_bytes()); request.push(0); let socket = UdpSocket::bind("0.0.0.0:0").await.map_err(|e| { + log::debug!("failed to bind UDP socket for SSRP: {}", e); err_protocol!("failed to bind UDP socket for SSRP: {}", e) })?; + log::debug!( + "sending SSRP CLNT_UCAST_INST request to {}:{} for instance '{}'", + server, + SSRP_PORT, + instance + ); + socket .send_to(&request, (server, SSRP_PORT)) .await .map_err(|e| { + log::debug!("failed to send SSRP request to {}:{}: {}", server, SSRP_PORT, e); err_protocol!("failed to send SSRP request to {}:{}: {}", server, SSRP_PORT, e) })?; @@ -29,6 +44,12 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul let bytes_read = timeout(SSRP_TIMEOUT, socket.recv(&mut buffer)) .await .map_err(|_| { + log::debug!( + "SSRP request to {} for instance {} timed out after {:?}", + server, + instance, + SSRP_TIMEOUT + ); err_protocol!( "SSRP request to {} for instance {} timed out after {:?}", server, @@ -37,6 +58,12 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul ) })? .map_err(|e| { + log::debug!( + "failed to receive SSRP response from {} for instance {}: {}", + server, + instance, + e + ); err_protocol!( "failed to receive SSRP response from {} for instance {}: {}", server, @@ -45,7 +72,14 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul ) })?; + log::debug!( + "received SSRP response from {} ({} bytes)", + server, + bytes_read + ); + if bytes_read < 3 { + log::debug!("SSRP response too short: {} bytes", bytes_read); return Err(err_protocol!( "SSRP response too short: {} bytes", bytes_read @@ -53,6 +87,10 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul } if buffer[0] != SVR_RESP { + log::debug!( + "invalid SSRP response type: expected 0x05, got 0x{:02x}", + buffer[0] + ); return Err(err_protocol!( "invalid SSRP response type: expected 0x05, got 0x{:02x}", buffer[0] @@ -61,6 +99,11 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul let response_size = u16::from_le_bytes([buffer[1], buffer[2]]) as usize; if response_size + 3 > bytes_read { + log::debug!( + "SSRP response size mismatch: expected {} bytes, got {}", + response_size + 3, + bytes_read + ); return Err(err_protocol!( "SSRP response size mismatch: expected {} bytes, got {}", response_size + 3, @@ -69,13 +112,22 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul } let response_data = String::from_utf8(buffer[3..(3 + response_size)].to_vec()) - .map_err(|e| err_protocol!("SSRP response is not valid UTF-8: {}", e))?; + .map_err(|e| { + log::debug!("SSRP response is not valid UTF-8: {}", e); + err_protocol!("SSRP response is not valid UTF-8: {}", e) + })?; + + log::debug!("SSRP response data: {}", response_data); parse_ssrp_response(&response_data, instance) } fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { let instances: Vec<&str> = data.split(";;").collect(); + log::debug!( + "parsing SSRP response, found {} instance entries", + instances.len() + ); for instance_data in instances { if instance_data.is_empty() { @@ -94,16 +146,42 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { } if let Some(name) = properties.get("InstanceName") { + log::debug!("found instance '{}' in SSRP response", name); + if name.eq_ignore_ascii_case(instance_name) { + log::debug!( + "instance '{}' matches requested instance '{}'", + name, + instance_name + ); + if let Some(tcp_port_str) = properties.get("tcp") { - return tcp_port_str.parse::().map_err(|e| { + let port = tcp_port_str.parse::().map_err(|e| { + log::debug!( + "invalid TCP port '{}' in SSRP response: {}", + tcp_port_str, + e + ); err_protocol!( "invalid TCP port '{}' in SSRP response: {}", tcp_port_str, e ) - }); + })?; + + log::debug!( + "resolved instance '{}' to port {}", + instance_name, + port + ); + + return Ok(port); } else { + log::debug!( + "instance '{}' found but no TCP port available in properties: {:?}", + instance_name, + properties.keys().collect::>() + ); return Err(err_protocol!( "instance '{}' found but no TCP port available", instance_name @@ -113,6 +191,10 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { } } + log::debug!( + "instance '{}' not found in SSRP response", + instance_name + ); Err(err_protocol!( "instance '{}' not found in SSRP response", instance_name diff --git a/sqlx-core/src/mssql/connection/stream.rs b/sqlx-core/src/mssql/connection/stream.rs index 3e637fca7c..8a383dedb9 100644 --- a/sqlx-core/src/mssql/connection/stream.rs +++ b/sqlx-core/src/mssql/connection/stream.rs @@ -57,7 +57,10 @@ impl MssqlStream { options.port }; + log::debug!("establishing TCP connection to {}:{}", options.host, port); let tcp_stream = TcpStream::connect((&*options.host, port)).await?; + log::debug!("TCP connection established to {}:{}", options.host, port); + let wrapped_stream = TlsPreloginWrapper::new(tcp_stream); let inner = BufStream::new(MaybeTlsStream::Raw(wrapped_stream)); From 2a083362bf47ce0760d375f96e7f50417a8ce1ee Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 22:42:53 +0000 Subject: [PATCH 03/10] Remove redundant debug logs from SSRP connection logic Co-authored-by: contact --- sqlx-core/src/mssql/connection/ssrp.rs | 43 +------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs index e590effdb7..c199e41659 100644 --- a/sqlx-core/src/mssql/connection/ssrp.rs +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -21,7 +21,6 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul request.push(0); let socket = UdpSocket::bind("0.0.0.0:0").await.map_err(|e| { - log::debug!("failed to bind UDP socket for SSRP: {}", e); err_protocol!("failed to bind UDP socket for SSRP: {}", e) })?; @@ -36,7 +35,6 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul .send_to(&request, (server, SSRP_PORT)) .await .map_err(|e| { - log::debug!("failed to send SSRP request to {}:{}: {}", server, SSRP_PORT, e); err_protocol!("failed to send SSRP request to {}:{}: {}", server, SSRP_PORT, e) })?; @@ -44,12 +42,6 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul let bytes_read = timeout(SSRP_TIMEOUT, socket.recv(&mut buffer)) .await .map_err(|_| { - log::debug!( - "SSRP request to {} for instance {} timed out after {:?}", - server, - instance, - SSRP_TIMEOUT - ); err_protocol!( "SSRP request to {} for instance {} timed out after {:?}", server, @@ -58,12 +50,6 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul ) })? .map_err(|e| { - log::debug!( - "failed to receive SSRP response from {} for instance {}: {}", - server, - instance, - e - ); err_protocol!( "failed to receive SSRP response from {} for instance {}: {}", server, @@ -79,7 +65,6 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul ); if bytes_read < 3 { - log::debug!("SSRP response too short: {} bytes", bytes_read); return Err(err_protocol!( "SSRP response too short: {} bytes", bytes_read @@ -87,10 +72,6 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul } if buffer[0] != SVR_RESP { - log::debug!( - "invalid SSRP response type: expected 0x05, got 0x{:02x}", - buffer[0] - ); return Err(err_protocol!( "invalid SSRP response type: expected 0x05, got 0x{:02x}", buffer[0] @@ -99,11 +80,6 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul let response_size = u16::from_le_bytes([buffer[1], buffer[2]]) as usize; if response_size + 3 > bytes_read { - log::debug!( - "SSRP response size mismatch: expected {} bytes, got {}", - response_size + 3, - bytes_read - ); return Err(err_protocol!( "SSRP response size mismatch: expected {} bytes, got {}", response_size + 3, @@ -112,10 +88,7 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul } let response_data = String::from_utf8(buffer[3..(3 + response_size)].to_vec()) - .map_err(|e| { - log::debug!("SSRP response is not valid UTF-8: {}", e); - err_protocol!("SSRP response is not valid UTF-8: {}", e) - })?; + .map_err(|e| err_protocol!("SSRP response is not valid UTF-8: {}", e))?; log::debug!("SSRP response data: {}", response_data); @@ -157,11 +130,6 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { if let Some(tcp_port_str) = properties.get("tcp") { let port = tcp_port_str.parse::().map_err(|e| { - log::debug!( - "invalid TCP port '{}' in SSRP response: {}", - tcp_port_str, - e - ); err_protocol!( "invalid TCP port '{}' in SSRP response: {}", tcp_port_str, @@ -177,11 +145,6 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { return Ok(port); } else { - log::debug!( - "instance '{}' found but no TCP port available in properties: {:?}", - instance_name, - properties.keys().collect::>() - ); return Err(err_protocol!( "instance '{}' found but no TCP port available", instance_name @@ -191,10 +154,6 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { } } - log::debug!( - "instance '{}' not found in SSRP response", - instance_name - ); Err(err_protocol!( "instance '{}' not found in SSRP response", instance_name From 6307c50e9e430c017dbe4b0c3924b9d6c061cc13 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 22:49:40 +0000 Subject: [PATCH 04/10] Refactor: Use String::from_utf8_lossy for SSRP response parsing Co-authored-by: contact --- sqlx-core/src/mssql/connection/ssrp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs index c199e41659..e6010cc518 100644 --- a/sqlx-core/src/mssql/connection/ssrp.rs +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -1,5 +1,6 @@ use crate::error::Error; use sqlx_rt::{timeout, UdpSocket}; +use std::borrow::Cow; use std::collections::HashMap; use std::time::Duration; @@ -87,15 +88,14 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul )); } - let response_data = String::from_utf8(buffer[3..(3 + response_size)].to_vec()) - .map_err(|e| err_protocol!("SSRP response is not valid UTF-8: {}", e))?; + let response_data = String::from_utf8_lossy(&buffer[3..(3 + response_size)]); log::debug!("SSRP response data: {}", response_data); parse_ssrp_response(&response_data, instance) } -fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { +fn parse_ssrp_response(data: &Cow<'_, str>, instance_name: &str) -> Result { let instances: Vec<&str> = data.split(";;").collect(); log::debug!( "parsing SSRP response, found {} instance entries", From df7bfdf2bdf97abdb56d061a23a7e69100f3d51f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 22:58:32 +0000 Subject: [PATCH 05/10] Checkpoint before follow-up message Co-authored-by: contact --- sqlx-core/src/mssql/connection/ssrp.rs | 75 +++++++++++++++++--------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs index e6010cc518..9af8b1eccc 100644 --- a/sqlx-core/src/mssql/connection/ssrp.rs +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -1,7 +1,6 @@ use crate::error::Error; +use encoding_rs::WINDOWS_1252; use sqlx_rt::{timeout, UdpSocket}; -use std::borrow::Cow; -use std::collections::HashMap; use std::time::Duration; const SSRP_PORT: u16 = 1434; @@ -9,6 +8,14 @@ const CLNT_UCAST_INST: u8 = 0x04; const SVR_RESP: u8 = 0x05; const SSRP_TIMEOUT: Duration = Duration::from_secs(1); +struct InstanceInfo<'a> { + server_name: Option<&'a str>, + instance_name: Option<&'a str>, + is_clustered: Option<&'a str>, + version: Option<&'a str>, + tcp_port: Option<&'a str>, +} + pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Result { log::debug!( "resolving SQL Server instance port for '{}' on server '{}'", @@ -88,37 +95,27 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul )); } - let response_data = String::from_utf8_lossy(&buffer[3..(3 + response_size)]); + let response_bytes = &buffer[3..(3 + response_size)]; + let (response_str, _encoding_used, had_errors) = WINDOWS_1252.decode(response_bytes); + + if had_errors { + log::debug!("SSRP response had MBCS decoding errors, continuing anyway"); + } - log::debug!("SSRP response data: {}", response_data); + log::debug!("SSRP response data: {}", response_str); - parse_ssrp_response(&response_data, instance) + parse_ssrp_response(&response_str, instance) } -fn parse_ssrp_response(data: &Cow<'_, str>, instance_name: &str) -> Result { - let instances: Vec<&str> = data.split(";;").collect(); - log::debug!( - "parsing SSRP response, found {} instance entries", - instances.len() - ); - - for instance_data in instances { +fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { + for instance_data in data.split(";;") { if instance_data.is_empty() { continue; } - let tokens: Vec<&str> = instance_data.split(';').collect(); - let mut properties: HashMap<&str, &str> = HashMap::new(); - - let mut i = 0; - while i + 1 < tokens.len() { - let key = tokens[i]; - let value = tokens[i + 1]; - properties.insert(key, value); - i += 2; - } - - if let Some(name) = properties.get("InstanceName") { + let info = parse_instance_info(instance_data); + + if let Some(name) = info.instance_name { log::debug!("found instance '{}' in SSRP response", name); if name.eq_ignore_ascii_case(instance_name) { @@ -128,7 +125,7 @@ fn parse_ssrp_response(data: &Cow<'_, str>, instance_name: &str) -> Result().map_err(|e| { err_protocol!( "invalid TCP port '{}' in SSRP response: {}", @@ -160,6 +157,32 @@ fn parse_ssrp_response(data: &Cow<'_, str>, instance_name: &str) -> Result(data: &'a str) -> InstanceInfo<'a> { + let mut info = InstanceInfo { + server_name: None, + instance_name: None, + is_clustered: None, + version: None, + tcp_port: None, + }; + + let mut tokens = data.split(';'); + while let Some(key) = tokens.next() { + let value = tokens.next(); + + match key { + "ServerName" => info.server_name = value, + "InstanceName" => info.instance_name = value, + "IsClustered" => info.is_clustered = value, + "Version" => info.version = value, + "tcp" => info.tcp_port = value, + _ => {} + } + } + + info +} + #[cfg(test)] mod tests { use super::*; From 9cb41cc640c141bbda3f7a3151084418a5cfbbd6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 22:59:59 +0000 Subject: [PATCH 06/10] Refactor: Improve SSRP error handling and logging Co-authored-by: contact --- sqlx-core/src/mssql/connection/ssrp.rs | 36 +++++++++++++----------- sqlx-core/src/mssql/connection/stream.rs | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs index 9af8b1eccc..aefd1210bd 100644 --- a/sqlx-core/src/mssql/connection/ssrp.rs +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -28,9 +28,9 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul request.extend_from_slice(instance.as_bytes()); request.push(0); - let socket = UdpSocket::bind("0.0.0.0:0").await.map_err(|e| { - err_protocol!("failed to bind UDP socket for SSRP: {}", e) - })?; + let socket = UdpSocket::bind("0.0.0.0:0") + .await + .map_err(|e| err_protocol!("failed to bind UDP socket for SSRP: {}", e))?; log::debug!( "sending SSRP CLNT_UCAST_INST request to {}:{} for instance '{}'", @@ -43,7 +43,12 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul .send_to(&request, (server, SSRP_PORT)) .await .map_err(|e| { - err_protocol!("failed to send SSRP request to {}:{}: {}", server, SSRP_PORT, e) + err_protocol!( + "failed to send SSRP request to {}:{}: {}", + server, + SSRP_PORT, + e + ) })?; let mut buffer = [0u8; 1024]; @@ -97,7 +102,7 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul let response_bytes = &buffer[3..(3 + response_size)]; let (response_str, _encoding_used, had_errors) = WINDOWS_1252.decode(response_bytes); - + if had_errors { log::debug!("SSRP response had MBCS decoding errors, continuing anyway"); } @@ -114,17 +119,17 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { } let info = parse_instance_info(instance_data); - + if let Some(name) = info.instance_name { log::debug!("found instance '{}' in SSRP response", name); - + if name.eq_ignore_ascii_case(instance_name) { log::debug!( "instance '{}' matches requested instance '{}'", name, instance_name ); - + if let Some(tcp_port_str) = info.tcp_port { let port = tcp_port_str.parse::().map_err(|e| { err_protocol!( @@ -133,13 +138,9 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { e ) })?; - - log::debug!( - "resolved instance '{}' to port {}", - instance_name, - port - ); - + + log::debug!("resolved instance '{}' to port {}", instance_name, port); + return Ok(port); } else { return Err(err_protocol!( @@ -169,7 +170,7 @@ fn parse_instance_info<'a>(data: &'a str) -> InstanceInfo<'a> { let mut tokens = data.split(';'); while let Some(key) = tokens.next() { let value = tokens.next(); - + match key { "ServerName" => info.server_name = value, "InstanceName" => info.instance_name = value, @@ -217,7 +218,8 @@ mod tests { #[test] fn test_parse_ssrp_response_no_tcp_port() { - let data = "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;;"; + let data = + "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;;"; let result = parse_ssrp_response(data, "SQLEXPRESS"); assert!(result.is_err()); } diff --git a/sqlx-core/src/mssql/connection/stream.rs b/sqlx-core/src/mssql/connection/stream.rs index 8a383dedb9..43d0aced4d 100644 --- a/sqlx-core/src/mssql/connection/stream.rs +++ b/sqlx-core/src/mssql/connection/stream.rs @@ -60,7 +60,7 @@ impl MssqlStream { log::debug!("establishing TCP connection to {}:{}", options.host, port); let tcp_stream = TcpStream::connect((&*options.host, port)).await?; log::debug!("TCP connection established to {}:{}", options.host, port); - + let wrapped_stream = TlsPreloginWrapper::new(tcp_stream); let inner = BufStream::new(MaybeTlsStream::Raw(wrapped_stream)); From a44d6deaff9f2a11859b5b5073fef0778940029b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 23:04:57 +0000 Subject: [PATCH 07/10] Fix: Correctly parse SSRP instance info types Co-authored-by: contact --- sqlx-core/src/mssql/connection/ssrp.rs | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs index aefd1210bd..bf6f86b71e 100644 --- a/sqlx-core/src/mssql/connection/ssrp.rs +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -11,9 +11,9 @@ const SSRP_TIMEOUT: Duration = Duration::from_secs(1); struct InstanceInfo<'a> { server_name: Option<&'a str>, instance_name: Option<&'a str>, - is_clustered: Option<&'a str>, + is_clustered: Option, version: Option<&'a str>, - tcp_port: Option<&'a str>, + tcp_port: Option, } pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Result { @@ -130,17 +130,8 @@ fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { instance_name ); - if let Some(tcp_port_str) = info.tcp_port { - let port = tcp_port_str.parse::().map_err(|e| { - err_protocol!( - "invalid TCP port '{}' in SSRP response: {}", - tcp_port_str, - e - ) - })?; - + if let Some(port) = info.tcp_port { log::debug!("resolved instance '{}' to port {}", instance_name, port); - return Ok(port); } else { return Err(err_protocol!( @@ -174,9 +165,17 @@ fn parse_instance_info<'a>(data: &'a str) -> InstanceInfo<'a> { match key { "ServerName" => info.server_name = value, "InstanceName" => info.instance_name = value, - "IsClustered" => info.is_clustered = value, + "IsClustered" => { + info.is_clustered = value.and_then(|v| match v { + "Yes" => Some(true), + "No" => Some(false), + _ => None, + }); + } "Version" => info.version = value, - "tcp" => info.tcp_port = value, + "tcp" => { + info.tcp_port = value.and_then(|v| v.parse::().ok()); + } _ => {} } } From 59a12b9c49fd822131e836a620daaecee2181335 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Nov 2025 23:10:38 +0000 Subject: [PATCH 08/10] Refactor: Rename ssrp parsing functions and improve logging Co-authored-by: contact --- sqlx-core/src/mssql/connection/ssrp.rs | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/sqlx-core/src/mssql/connection/ssrp.rs b/sqlx-core/src/mssql/connection/ssrp.rs index bf6f86b71e..c9a9e413bc 100644 --- a/sqlx-core/src/mssql/connection/ssrp.rs +++ b/sqlx-core/src/mssql/connection/ssrp.rs @@ -109,10 +109,10 @@ pub(crate) async fn resolve_instance_port(server: &str, instance: &str) -> Resul log::debug!("SSRP response data: {}", response_str); - parse_ssrp_response(&response_str, instance) + find_instance_tcp_port(&response_str, instance) } -fn parse_ssrp_response(data: &str, instance_name: &str) -> Result { +fn find_instance_tcp_port(data: &str, instance_name: &str) -> Result { for instance_data in data.split(";;") { if instance_data.is_empty() { continue; @@ -176,7 +176,11 @@ fn parse_instance_info<'a>(data: &'a str) -> InstanceInfo<'a> { "tcp" => { info.tcp_port = value.and_then(|v| v.parse::().ok()); } - _ => {} + _ => { + if !key.is_empty() { + log::debug!("ignoring unknown SSRP key: '{}'", key); + } + } } } @@ -188,38 +192,38 @@ mod tests { use super::*; #[test] - fn test_parse_ssrp_response_single_instance() { + fn test_find_instance_tcp_port_single_instance() { let data = "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;tcp;1433;;"; - let port = parse_ssrp_response(data, "SQLEXPRESS").unwrap(); + let port = find_instance_tcp_port(data, "SQLEXPRESS").unwrap(); assert_eq!(port, 1433); } #[test] - fn test_parse_ssrp_response_multiple_instances() { + fn test_find_instance_tcp_port_multiple_instances() { let data = "ServerName;SRV1;InstanceName;INST1;IsClustered;No;Version;15.0.2000.5;tcp;1433;;ServerName;SRV1;InstanceName;INST2;IsClustered;No;Version;16.0.1000.6;tcp;1434;np;\\\\SRV1\\pipe\\MSSQL$INST2\\sql\\query;;"; - let port = parse_ssrp_response(data, "INST2").unwrap(); + let port = find_instance_tcp_port(data, "INST2").unwrap(); assert_eq!(port, 1434); } #[test] - fn test_parse_ssrp_response_case_insensitive() { + fn test_find_instance_tcp_port_case_insensitive() { let data = "ServerName;MYSERVER;InstanceName;SQLExpress;IsClustered;No;Version;15.0.2000.5;tcp;1433;;"; - let port = parse_ssrp_response(data, "sqlexpress").unwrap(); + let port = find_instance_tcp_port(data, "sqlexpress").unwrap(); assert_eq!(port, 1433); } #[test] - fn test_parse_ssrp_response_instance_not_found() { + fn test_find_instance_tcp_port_instance_not_found() { let data = "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;tcp;1433;;"; - let result = parse_ssrp_response(data, "NOTFOUND"); + let result = find_instance_tcp_port(data, "NOTFOUND"); assert!(result.is_err()); } #[test] - fn test_parse_ssrp_response_no_tcp_port() { + fn test_find_instance_tcp_port_no_tcp_port() { let data = "ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;;"; - let result = parse_ssrp_response(data, "SQLEXPRESS"); + let result = find_instance_tcp_port(data, "SQLEXPRESS"); assert!(result.is_err()); } } From 35323786da0b75ec70677697acc634d695771e14 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Nov 2025 08:49:44 +0000 Subject: [PATCH 09/10] feat: Improve mssql port resolution logic Co-authored-by: contact --- sqlx-core/src/mssql/connection/stream.rs | 25 ++++++++++++++++++++---- sqlx-core/src/mssql/options/mod.rs | 19 +++++++++++++----- sqlx-core/src/mssql/options/parse.rs | 4 ++-- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/sqlx-core/src/mssql/connection/stream.rs b/sqlx-core/src/mssql/connection/stream.rs index 43d0aced4d..0911d4d235 100644 --- a/sqlx-core/src/mssql/connection/stream.rs +++ b/sqlx-core/src/mssql/connection/stream.rs @@ -51,10 +51,27 @@ pub(crate) struct MssqlStream { impl MssqlStream { pub(super) async fn connect(options: &MssqlConnectOptions) -> Result { - let port = if let Some(ref instance) = options.instance { - super::ssrp::resolve_instance_port(&options.host, instance).await? - } else { - options.port + let port = match (options.port, &options.instance) { + (Some(port), _) => { + log::debug!( + "using explicitly specified port {} for host '{}'", + port, + options.host + ); + port + } + (None, Some(instance)) => { + super::ssrp::resolve_instance_port(&options.host, instance).await? + } + (None, None) => { + const DEFAULT_PORT: u16 = 1433; + log::debug!( + "using default port {} for host '{}'", + DEFAULT_PORT, + options.host + ); + DEFAULT_PORT + } }; log::debug!("establishing TCP connection to {}:{}", options.host, port); diff --git a/sqlx-core/src/mssql/options/mod.rs b/sqlx-core/src/mssql/options/mod.rs index 9dc72945a0..4d7f97ba63 100644 --- a/sqlx-core/src/mssql/options/mod.rs +++ b/sqlx-core/src/mssql/options/mod.rs @@ -13,15 +13,24 @@ mod parse; /// mssql://[username[:password]@]host[:port]/database[?param1=value1¶m2=value2...] /// ``` /// -/// When connecting to a named instance, use the `instance` parameter: +/// Port resolution priority: +/// 1. If an explicit port is specified, it is always used +/// 2. If a named instance is specified via `?instance=NAME`, the port is discovered via SSRP +/// 3. Otherwise, the default port 1433 is used +/// +/// Example with named instance (port auto-discovered): /// ```text /// mssql://user:pass@localhost/mydb?instance=SQLEXPRESS /// ``` -/// The port will be automatically discovered using the SQL Server Resolution Protocol (SSRP). +/// +/// Example with explicit port (SSRP not used): +/// ```text +/// mssql://user:pass@localhost:1434/mydb?instance=SQLEXPRESS +/// ``` #[derive(Debug, Clone)] pub struct MssqlConnectOptions { pub(crate) host: String, - pub(crate) port: u16, + pub(crate) port: Option, pub(crate) username: String, pub(crate) database: String, pub(crate) password: Option, @@ -51,7 +60,7 @@ impl Default for MssqlConnectOptions { impl MssqlConnectOptions { pub fn new() -> Self { Self { - port: 1433, + port: None, host: String::from("localhost"), database: String::from("master"), username: String::from("sa"), @@ -79,7 +88,7 @@ impl MssqlConnectOptions { } pub fn port(mut self, port: u16) -> Self { - self.port = port; + self.port = Some(port); self } diff --git a/sqlx-core/src/mssql/options/parse.rs b/sqlx-core/src/mssql/options/parse.rs index 68f7214e2b..b1c7cb79a7 100644 --- a/sqlx-core/src/mssql/options/parse.rs +++ b/sqlx-core/src/mssql/options/parse.rs @@ -19,11 +19,11 @@ impl FromStr for MssqlConnectOptions { /// - `username`: The username for SQL Server authentication. /// - `password`: The password for SQL Server authentication. /// - `host`: The hostname or IP address of the SQL Server. - /// - `port`: The port number (default is 1433). + /// - `port`: The port number. If not specified, defaults to 1433 or is discovered via SSRP when using named instances. /// - `database`: The name of the database to connect to. /// /// Supported query parameters: - /// - `instance`: SQL Server named instance. When specified, the port is automatically discovered using the SQL Server Resolution Protocol (SSRP). + /// - `instance`: SQL Server named instance. When specified without an explicit port, the port is automatically discovered using the SQL Server Resolution Protocol (SSRP). If a port is explicitly specified, SSRP is not used. /// - `encrypt`: Controls connection encryption: /// - `strict`: Requires encryption and validates the server certificate. /// - `mandatory` or `true` or `yes`: Requires encryption but doesn't validate the server certificate. From ca26597737f243b43e6269705b62c3e995df22c7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Nov 2025 09:17:27 +0000 Subject: [PATCH 10/10] feat: Add SQL Server named instance support with SSRP Co-authored-by: contact --- CHANGELOG.md | 3 +++ Cargo.lock | 10 +++++----- Cargo.toml | 6 +++--- README.md | 1 + examples/postgres/axum-social-with-tests/Cargo.toml | 2 +- sqlx-bench/Cargo.toml | 4 ++-- sqlx-cli/Cargo.toml | 4 ++-- sqlx-core/Cargo.toml | 4 ++-- sqlx-macros/Cargo.toml | 6 +++--- sqlx-rt/Cargo.toml | 2 +- 10 files changed, 23 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 855e1aa6c0..b810b78372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.6.50 + - Added support for SQL Server named instances with automatic port discovery via SSRP (SQL Server Resolution Protocol). You can now connect using `mssql://user:pass@host/db?instance=SQLEXPRESS` and the port will be automatically discovered. + ## 0.6.49 - Added support for ODBC. SQLx-oldapi can now connect to Oracle, Db2, Snowflake, BigQuery, Databricks, and many other databases, using locally installed ODBC drivers. diff --git a/Cargo.lock b/Cargo.lock index 6fa3d9ddfe..661b294b6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3990,7 +3990,7 @@ dependencies = [ [[package]] name = "sqlx-cli" -version = "0.6.49" +version = "0.6.50" dependencies = [ "anyhow", "async-trait", @@ -4015,7 +4015,7 @@ dependencies = [ [[package]] name = "sqlx-core-oldapi" -version = "0.6.49" +version = "0.6.50" dependencies = [ "ahash 0.8.12", "atoi", @@ -4177,7 +4177,7 @@ dependencies = [ [[package]] name = "sqlx-macros-oldapi" -version = "0.6.49" +version = "0.6.50" dependencies = [ "dotenvy", "either", @@ -4197,7 +4197,7 @@ dependencies = [ [[package]] name = "sqlx-oldapi" -version = "0.6.49" +version = "0.6.50" dependencies = [ "anyhow", "async-std", @@ -4225,7 +4225,7 @@ dependencies = [ [[package]] name = "sqlx-rt-oldapi" -version = "0.6.49" +version = "0.6.50" dependencies = [ "async-native-tls", "async-std", diff --git a/Cargo.toml b/Cargo.toml index 8189ed9936..53dbe15d6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ [package] name = "sqlx-oldapi" -version = "0.6.49" +version = "0.6.50" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/lovasoa/sqlx" @@ -155,8 +155,8 @@ bstr = ["sqlx-core/bstr"] git2 = ["sqlx-core/git2"] [dependencies] -sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.49", path = "sqlx-core", default-features = false } -sqlx-macros = { package = "sqlx-macros-oldapi", version = "0.6.49", path = "sqlx-macros", default-features = false, optional = true } +sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.50", path = "sqlx-core", default-features = false } +sqlx-macros = { package = "sqlx-macros-oldapi", version = "0.6.50", path = "sqlx-macros", default-features = false, optional = true } [dev-dependencies] anyhow = "1.0.52" diff --git a/README.md b/README.md index 2683c4675c..bb846bb6d6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ > - Multiple bug fixes around string handling, including better support for long strings > - Support for packet chunking, which fixes a bug where large bound parameters or large queries would fail > - Support for TLS encrypted connections +> - Support for named instances with automatic port discovery via SSRP > > The main use case driving the development of sqlx-oldapi is the [SQLPage](https://sql.datapage.app/) SQL-only rapid application building tool. diff --git a/examples/postgres/axum-social-with-tests/Cargo.toml b/examples/postgres/axum-social-with-tests/Cargo.toml index b495844988..08f0e5615e 100644 --- a/examples/postgres/axum-social-with-tests/Cargo.toml +++ b/examples/postgres/axum-social-with-tests/Cargo.toml @@ -9,7 +9,7 @@ publish = false [dependencies] # Primary crates axum = { version = "0.5.13", features = ["macros"] } -sqlx = { package = "sqlx-oldapi", version = "0.6.49", path = "../../../", features = ["runtime-tokio-rustls", "postgres", "time", "uuid"] } +sqlx = { package = "sqlx-oldapi", version = "0.6.50", path = "../../../", features = ["runtime-tokio-rustls", "postgres", "time", "uuid"] } tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] } # Important secondary crates diff --git a/sqlx-bench/Cargo.toml b/sqlx-bench/Cargo.toml index b2ecbd9762..2742424ca0 100644 --- a/sqlx-bench/Cargo.toml +++ b/sqlx-bench/Cargo.toml @@ -33,8 +33,8 @@ sqlite = ["sqlx/sqlite"] criterion = "0.3.3" dotenvy = "0.15.0" once_cell = "1.4" -sqlx = { package = "sqlx-oldapi", version = "0.6.49", path = "../", default-features = false, features = ["macros"] } -sqlx-rt = { package = "sqlx-rt-oldapi", version = "0.6.49", path = "../sqlx-rt", default-features = false } +sqlx = { package = "sqlx-oldapi", version = "0.6.50", path = "../", default-features = false, features = ["macros"] } +sqlx-rt = { package = "sqlx-rt-oldapi", version = "0.6.50", path = "../sqlx-rt", default-features = false } chrono = "0.4.19" diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index 3af987b2eb..bc3c155acb 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlx-cli" -version = "0.6.49" +version = "0.6.50" description = "Command-line utility for SQLx, the Rust SQL toolkit." edition = "2021" readme = "README.md" @@ -28,7 +28,7 @@ path = "src/bin/cargo-sqlx.rs" [dependencies] dotenvy = "0.15.0" tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread"] } -sqlx = { package = "sqlx-oldapi", version = "0.6.49", path = "..", default-features = false, features = [ +sqlx = { package = "sqlx-oldapi", version = "0.6.50", path = "..", default-features = false, features = [ "migrate", "any", "offline", diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index d29f2ca65f..709a265e6d 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlx-core-oldapi" -version = "0.6.49" +version = "0.6.50" repository = "https://github.com/lovasoa/sqlx" description = "Core of SQLx, the rust SQL toolkit. Not intended to be used directly." license = "MIT OR Apache-2.0" @@ -104,7 +104,7 @@ offline = ["serde", "either/serde"] paste = "1.0.6" ahash = "0.8.3" atoi = "2.0.0" -sqlx-rt = { path = "../sqlx-rt", version = "0.6.49", package = "sqlx-rt-oldapi" } +sqlx-rt = { path = "../sqlx-rt", version = "0.6.50", package = "sqlx-rt-oldapi" } base64 = { version = "0.22", default-features = false, optional = true, features = ["std"] } bigdecimal_ = { version = "0.4.1", optional = true, package = "bigdecimal" } rust_decimal = { version = "1.19.0", optional = true } diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index a41ae7ab5a..5b3ffc6307 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlx-macros-oldapi" -version = "0.6.49" +version = "0.6.50" repository = "https://github.com/lovasoa/sqlx" description = "Macros for SQLx, the rust SQL toolkit. Not intended to be used directly." license = "MIT OR Apache-2.0" @@ -75,8 +75,8 @@ heck = { version = "0.5" } either = "1.6.1" once_cell = "1.9.0" proc-macro2 = { version = "1.0.36", default-features = false } -sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.49", default-features = false, features = ["any", "aws_lc_rs", "tls12"], path = "../sqlx-core" } -sqlx-rt = { version = "0.6.49", default-features = false, path = "../sqlx-rt", package = "sqlx-rt-oldapi", features = ["aws_lc_rs"] } +sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.50", default-features = false, features = ["any", "aws_lc_rs", "tls12"], path = "../sqlx-core" } +sqlx-rt = { version = "0.6.50", default-features = false, path = "../sqlx-rt", package = "sqlx-rt-oldapi", features = ["aws_lc_rs"] } serde = { version = "1.0.132", features = ["derive"], optional = true } serde_json = { version = "1.0.73", optional = true } sha2 = { version = "0.10.0", optional = true } diff --git a/sqlx-rt/Cargo.toml b/sqlx-rt/Cargo.toml index 3aeea52084..1aaf3762a8 100644 --- a/sqlx-rt/Cargo.toml +++ b/sqlx-rt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlx-rt-oldapi" -version = "0.6.49" +version = "0.6.50" repository = "https://github.com/launchbadge/sqlx" license = "MIT OR Apache-2.0" description = "Runtime abstraction used by SQLx, the Rust SQL toolkit. Not intended to be used directly."