From abe0175c96c9b487cbfc1e09be3dceced8c54394 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 16:18:53 +0200 Subject: [PATCH 01/11] fix: satisfy clippy on latest stable --- sqlx-core/src/logger.rs | 4 ++-- sqlx-core/src/mssql/connection/stream.rs | 2 +- sqlx-core/src/postgres/connection/sasl.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlx-core/src/logger.rs b/sqlx-core/src/logger.rs index 6cfd80d215..ec52f04664 100644 --- a/sqlx-core/src/logger.rs +++ b/sqlx-core/src/logger.rs @@ -51,7 +51,7 @@ impl<'q> QueryLogger<'q> { let sql = if summary != self.sql { summary.push_str(" …"); - format!("\n\n{}\n", &self.sql) + format!("\n\n{}\n", self.sql) } else { String::new() }; @@ -125,7 +125,7 @@ impl<'q, O: Debug + Hash + Eq, R: Debug, P: Debug> QueryPlanLogger<'q, O, R, P> let sql = if summary != self.sql { summary.push_str(" …"); - format!("\n\n{}\n", &self.sql) + format!("\n\n{}\n", self.sql) } else { String::new() }; diff --git a/sqlx-core/src/mssql/connection/stream.rs b/sqlx-core/src/mssql/connection/stream.rs index 0911d4d235..8a35beae30 100644 --- a/sqlx-core/src/mssql/connection/stream.rs +++ b/sqlx-core/src/mssql/connection/stream.rs @@ -342,7 +342,7 @@ pub(crate) fn write_packets<'en, T: Encode<'en>>( ); } - packet_header.truncate(0); + packet_header.clear(); PacketHeader { r#type: ty, status: if is_last { diff --git a/sqlx-core/src/postgres/connection/sasl.rs b/sqlx-core/src/postgres/connection/sasl.rs index 6e6ee68339..665590d313 100644 --- a/sqlx-core/src/postgres/connection/sasl.rs +++ b/sqlx-core/src/postgres/connection/sasl.rs @@ -113,7 +113,7 @@ pub(crate) async fn authenticate( let client_final_message_wo_proof = format!( "{channel_binding},r={nonce}", channel_binding = channel_binding, - nonce = &cont.nonce + nonce = cont.nonce ); // AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof From ceef25f4cb51d79c3c33e7dc30f98296d32f7394 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 16:49:17 +0200 Subject: [PATCH 02/11] ci: pin clippy to Rust 1.98.1 --- .github/workflows/sqlx.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index a147a0d267..95771e2afb 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -22,6 +22,11 @@ jobs: tls: [native-tls, rustls] steps: - uses: actions/checkout@v6 + - name: Install pinned Clippy toolchain + uses: dtolnay/rust-toolchain@ce678459e9fc7500d337468f904b95f1b5c10b5e + with: + toolchain: 1.98.1 + components: clippy - uses: Swatinem/rust-cache@65012b490220f477f20ab979e35ae732e6de4e68 with: prefix-key: v1-sqlx From 5b27970ee9cfaf1006ed5bb9366ab5aaa3e7ac52 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 16:55:39 +0200 Subject: [PATCH 03/11] fix(clippy): remove redundant formatting borrow --- sqlx-macros/src/query/output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-macros/src/query/output.rs b/sqlx-macros/src/query/output.rs index a08451ed87..4e1f0c6787 100644 --- a/sqlx-macros/src/query/output.rs +++ b/sqlx-macros/src/query/output.rs @@ -226,7 +226,7 @@ fn get_column_type(i: usize, column: &DB::Column) -> TokenStrea if let Some(feature_gate) = ::get_feature_gate(type_info) { format!( "optional feature `{feat}` required for type {ty} of {col}", - ty = &type_info, + ty = type_info, feat = feature_gate, col = DisplayColumn { idx: i, From 9e089c59fc0f77c0f4615b360c69a9b0cec13a1a Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 17:03:39 +0200 Subject: [PATCH 04/11] test: remove useless format in postgres type fixture --- tests/postgres/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index 72914e0293..20f52fe426 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -69,7 +69,7 @@ test_type!(str<&str>(Postgres, )); test_type!(string(Postgres, - "'this is foo'" == format!("this is foo"), + "'this is foo'" == "this is foo".to_string(), )); test_type!(string_vec>(Postgres, From db31afbbd4413b535f58e9225797473f7665bb24 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 17:09:21 +0200 Subject: [PATCH 05/11] ci: use pinned toolchain action without ignored input --- .github/workflows/sqlx.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index 95771e2afb..5162d034b9 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -25,7 +25,6 @@ jobs: - name: Install pinned Clippy toolchain uses: dtolnay/rust-toolchain@ce678459e9fc7500d337468f904b95f1b5c10b5e with: - toolchain: 1.98.1 components: clippy - uses: Swatinem/rust-cache@65012b490220f477f20ab979e35ae732e6de4e68 with: From acdd7afbc8c30fac078c8f9187ccffed17007f3e Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 15:16:00 +0200 Subject: [PATCH 06/11] fix(mssql): declare NULL parameters using their Rust type --- sqlx-core/src/mssql/arguments.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/mssql/arguments.rs b/sqlx-core/src/mssql/arguments.rs index 98e6de84be..23d9ecaa2d 100644 --- a/sqlx-core/src/mssql/arguments.rs +++ b/sqlx-core/src/mssql/arguments.rs @@ -65,6 +65,13 @@ impl MssqlArguments { T: Encode<'q, Mssql> + Type, { let ty = value.produces().unwrap_or_else(T::type_info); + // `DataType::Null` is required in TDS for null values, but the + // `sp_executesql` declaration needs the parameter's Rust type. + let declaration_ty = if ty.0.is_null() { + T::type_info() + } else { + ty.clone() + }; // produce an ordinal parameter name // @p1, @p2, ... @pN @@ -91,7 +98,7 @@ impl MssqlArguments { declarations.push_str(name); declarations.push(' '); - ty.0.fmt(declarations); + declaration_ty.0.fmt(declarations); // write out the parameter From bf5737c4ab3be82ea3104651fbf6c703e1edb2b2 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 16:51:26 +0200 Subject: [PATCH 07/11] test(mssql): cover null parameter declarations --- sqlx-core/src/mssql/arguments.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sqlx-core/src/mssql/arguments.rs b/sqlx-core/src/mssql/arguments.rs index 23d9ecaa2d..438f143d18 100644 --- a/sqlx-core/src/mssql/arguments.rs +++ b/sqlx-core/src/mssql/arguments.rs @@ -136,8 +136,21 @@ impl<'q> Arguments<'q> for MssqlArguments { #[cfg(test)] mod tests { use super::*; + use crate::mssql::protocol::type_info::DataType; use crate::query_builder::QueryBuilder; + #[test] + fn test_null_argument_uses_bound_type_for_declaration() { + let mut args = MssqlArguments::default(); + args.add(None::); + + assert_eq!(args.declarations, "@p1 nvarchar(max)"); + + // The SQL declaration uses String's type, while TDS still carries NULLTYPE. + let type_info_offset = 1 + usize::from(args.data[0]) * 2 + 1; + assert_eq!(args.data[type_info_offset], DataType::Null as u8); + } + #[test] fn test_format_placeholder_method() { let mut args = MssqlArguments::default(); // ordinal = 0 initially From 52b77c0e09ccf78bf979fa11fa53056ce1edc9c6 Mon Sep 17 00:00:00 2001 From: Ophir Lojkine Date: Wed, 23 Sep 2026 16:04:59 +0000 Subject: [PATCH 08/11] test(mssql): replace null declaration test with ISNULL query covering #58 Replace test_null_argument_uses_bound_type_for_declaration with a test that builds SELECT ISNULL(@p1, N'hello') via QueryBuilder, referencing https://github.com/sqlpage/sqlx-oldapi/issues/58 --- sqlx-core/src/mssql/arguments.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/mssql/arguments.rs b/sqlx-core/src/mssql/arguments.rs index 438f143d18..221b9a74c6 100644 --- a/sqlx-core/src/mssql/arguments.rs +++ b/sqlx-core/src/mssql/arguments.rs @@ -139,16 +139,32 @@ mod tests { use crate::mssql::protocol::type_info::DataType; use crate::query_builder::QueryBuilder; + // Regression test for https://github.com/sqlpage/sqlx-oldapi/issues/58 + // + // When a null argument is bound, the `sp_executesql` declaration must use the + // Rust type of the argument (e.g. `nvarchar(5)`) instead of `NULLTYPE`. + // Otherwise, SQL Server cannot coerce the parameter type in expressions like + // `ISNULL(@p1, N'hello')`, where the fallback literal determines the result type. #[test] - fn test_null_argument_uses_bound_type_for_declaration() { + fn test_null_argument_declaration_in_isnull_expression() { let mut args = MssqlArguments::default(); args.add(None::); + // The declaration must come from String's type, not DataType::Null. assert_eq!(args.declarations, "@p1 nvarchar(max)"); - // The SQL declaration uses String's type, while TDS still carries NULLTYPE. + // ...while TDS still carries NULLTYPE for the value itself. let type_info_offset = 1 + usize::from(args.data[0]) * 2 + 1; assert_eq!(args.data[type_info_offset], DataType::Null as u8); + + // Simulate the full query that triggered issue #58: the parameter is + // null, but `sp_executesql` still needs a concrete declared type so + // `ISNULL(@p1, N'hello')` compiles. + let mut builder = QueryBuilder::::new("SELECT ISNULL("); + builder.push_bind(None::).push(", N'hello')"); + let sql = builder.sql(); + + assert_eq!(sql, "SELECT ISNULL(@p1, N'hello')"); } #[test] From 389cdf57fca29c6bacf0ad6ba4a4a86e98c8316a Mon Sep 17 00:00:00 2001 From: Ophir Lojkine Date: Wed, 23 Sep 2026 16:09:36 +0000 Subject: [PATCH 09/11] test(mssql): e2e test for ISNULL with null parameter (issue #58) Replace unit test with an end-to-end test asserting SELECT ISNULL(@p1, N'hello') with a null bind returns 'hello'. Closes https://github.com/sqlpage/sqlx-oldapi/issues/58 --- sqlx-core/src/mssql/arguments.rs | 28 ---------------------------- tests/mssql/mssql.rs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/sqlx-core/src/mssql/arguments.rs b/sqlx-core/src/mssql/arguments.rs index 221b9a74c6..01f82ccea6 100644 --- a/sqlx-core/src/mssql/arguments.rs +++ b/sqlx-core/src/mssql/arguments.rs @@ -136,36 +136,8 @@ impl<'q> Arguments<'q> for MssqlArguments { #[cfg(test)] mod tests { use super::*; - use crate::mssql::protocol::type_info::DataType; use crate::query_builder::QueryBuilder; - // Regression test for https://github.com/sqlpage/sqlx-oldapi/issues/58 - // - // When a null argument is bound, the `sp_executesql` declaration must use the - // Rust type of the argument (e.g. `nvarchar(5)`) instead of `NULLTYPE`. - // Otherwise, SQL Server cannot coerce the parameter type in expressions like - // `ISNULL(@p1, N'hello')`, where the fallback literal determines the result type. - #[test] - fn test_null_argument_declaration_in_isnull_expression() { - let mut args = MssqlArguments::default(); - args.add(None::); - - // The declaration must come from String's type, not DataType::Null. - assert_eq!(args.declarations, "@p1 nvarchar(max)"); - - // ...while TDS still carries NULLTYPE for the value itself. - let type_info_offset = 1 + usize::from(args.data[0]) * 2 + 1; - assert_eq!(args.data[type_info_offset], DataType::Null as u8); - - // Simulate the full query that triggered issue #58: the parameter is - // null, but `sp_executesql` still needs a concrete declared type so - // `ISNULL(@p1, N'hello')` compiles. - let mut builder = QueryBuilder::::new("SELECT ISNULL("); - builder.push_bind(None::).push(", N'hello')"); - let sql = builder.sql(); - - assert_eq!(sql, "SELECT ISNULL(@p1, N'hello')"); - } #[test] fn test_format_placeholder_method() { diff --git a/tests/mssql/mssql.rs b/tests/mssql/mssql.rs index 90cc8c81d4..cc4faf5701 100644 --- a/tests/mssql/mssql.rs +++ b/tests/mssql/mssql.rs @@ -193,6 +193,21 @@ async fn it_binds_empty_string_and_null() -> anyhow::Result<()> { Ok(()) } +// Regression test for https://github.com/sqlpage/sqlx-oldapi/issues/58 +#[sqlx_macros::test] +async fn it_binds_null_with_type_in_isnull_expression() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let (val,): (String,) = sqlx_oldapi::query_as("SELECT ISNULL(@p1, N'hello')") + .bind(None::) + .fetch_one(&mut conn) + .await?; + + assert_eq!(val, "hello"); + + Ok(()) +} + #[sqlx_macros::test] async fn it_binds_string_with_special_chars() -> anyhow::Result<()> { let mut conn = new::().await?; From bfcf3778a00c1ef8138960cf98978203dce99145 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 19:18:56 +0200 Subject: [PATCH 10/11] fmt --- sqlx-core/src/mssql/arguments.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/sqlx-core/src/mssql/arguments.rs b/sqlx-core/src/mssql/arguments.rs index 01f82ccea6..23d9ecaa2d 100644 --- a/sqlx-core/src/mssql/arguments.rs +++ b/sqlx-core/src/mssql/arguments.rs @@ -138,7 +138,6 @@ mod tests { use super::*; use crate::query_builder::QueryBuilder; - #[test] fn test_format_placeholder_method() { let mut args = MssqlArguments::default(); // ordinal = 0 initially From 3443810a504ed2532035cdafbb070fa1ecda50ba Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 23 Sep 2026 20:58:07 +0200 Subject: [PATCH 11/11] Fix MSSQL null varbinary declarations --- sqlx-core/src/mssql/types/bytes.rs | 2 +- sqlx-core/src/mssql/types/json.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/mssql/types/bytes.rs b/sqlx-core/src/mssql/types/bytes.rs index 48e0f71318..825d4f6cbe 100644 --- a/sqlx-core/src/mssql/types/bytes.rs +++ b/sqlx-core/src/mssql/types/bytes.rs @@ -8,7 +8,7 @@ use std::borrow::Cow; impl Type for [u8] { fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::BigVarBinary, 0)) + MssqlTypeInfo(TypeInfo::new(DataType::BigVarBinary, 0xFF_FF)) } fn compatible(ty: &MssqlTypeInfo) -> bool { diff --git a/sqlx-core/src/mssql/types/json.rs b/sqlx-core/src/mssql/types/json.rs index 1dd67a2132..3f5aa3bfec 100644 --- a/sqlx-core/src/mssql/types/json.rs +++ b/sqlx-core/src/mssql/types/json.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; impl Type for Json { fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::BigVarBinary, 0)) + MssqlTypeInfo(TypeInfo::new(DataType::BigVarBinary, 0xFF_FF)) } fn compatible(ty: &MssqlTypeInfo) -> bool {