From 1c64adbebfee8857f6cf9c8dd90e2fb34bb174b4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 25 Sep 2025 18:47:46 +0000 Subject: [PATCH 01/10] Fix: Decode column names using UTF-16 Co-authored-by: contact --- sqlx-core/src/odbc/connection/mod.rs | 7 +++++-- sqlx-core/src/odbc/connection/odbc_bridge.rs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sqlx-core/src/odbc/connection/mod.rs b/sqlx-core/src/odbc/connection/mod.rs index 391f118351..ac3223d9a3 100644 --- a/sqlx-core/src/odbc/connection/mod.rs +++ b/sqlx-core/src/odbc/connection/mod.rs @@ -40,8 +40,11 @@ fn create_column(stmt: &mut PreparedStatement, index: u16) -> OdbcColumn { } } -fn decode_column_name(name_bytes: Vec, index: u16) -> String { - String::from_utf8(name_bytes).unwrap_or_else(|_| format!("col{}", index - 1)) +fn decode_column_name(name_words: Vec, index: u16) -> String { + match String::from_utf16(&name_words) { + Ok(s) => s, + Err(_) => format!("col{}", index - 1), + } } /// A connection to an ODBC-accessible database. diff --git a/sqlx-core/src/odbc/connection/odbc_bridge.rs b/sqlx-core/src/odbc/connection/odbc_bridge.rs index d0e20262e3..eb6b319ed3 100644 --- a/sqlx-core/src/odbc/connection/odbc_bridge.rs +++ b/sqlx-core/src/odbc/connection/odbc_bridge.rs @@ -139,8 +139,11 @@ where } } -fn decode_column_name(name_bytes: Vec, index: u16) -> String { - String::from_utf8(name_bytes).unwrap_or_else(|_| format!("col{}", index - 1)) +fn decode_column_name(name_words: Vec, index: u16) -> String { + match String::from_utf16(&name_words) { + Ok(s) => s, + Err(_) => format!("col{}", index - 1), + } } fn stream_rows(cursor: &mut C, columns: &[OdbcColumn], tx: &ExecuteSender) -> Result From bf3a5b716e9cd0e633d9af58276a48fec46430e0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 25 Sep 2025 20:28:10 +0000 Subject: [PATCH 02/10] Checkpoint before follow-up message Co-authored-by: contact --- sqlx-core/src/odbc/connection/mod.rs | 21 ++++++++++++++++---- sqlx-core/src/odbc/connection/odbc_bridge.rs | 21 ++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/sqlx-core/src/odbc/connection/mod.rs b/sqlx-core/src/odbc/connection/mod.rs index ac3223d9a3..df1a52b8a9 100644 --- a/sqlx-core/src/odbc/connection/mod.rs +++ b/sqlx-core/src/odbc/connection/mod.rs @@ -40,13 +40,26 @@ fn create_column(stmt: &mut PreparedStatement, index: u16) -> OdbcColumn { } } -fn decode_column_name(name_words: Vec, index: u16) -> String { - match String::from_utf16(&name_words) { - Ok(s) => s, - Err(_) => format!("col{}", index - 1), +trait ColumnNameDecode { + fn decode_or_default(self, index: u16) -> String; +} + +impl ColumnNameDecode for Vec { + fn decode_or_default(self, index: u16) -> String { + String::from_utf8(self).unwrap_or_else(|_| format!("col{}", index - 1)) } } +impl ColumnNameDecode for Vec { + fn decode_or_default(self, index: u16) -> String { + String::from_utf16(&self).unwrap_or_else(|_| format!("col{}", index - 1)) + } +} + +fn decode_column_name(name: T, index: u16) -> String { + name.decode_or_default(index) +} + /// A connection to an ODBC-accessible database. /// /// ODBC uses a blocking C API, so we offload blocking calls to the runtime's blocking diff --git a/sqlx-core/src/odbc/connection/odbc_bridge.rs b/sqlx-core/src/odbc/connection/odbc_bridge.rs index eb6b319ed3..0646f8c5ef 100644 --- a/sqlx-core/src/odbc/connection/odbc_bridge.rs +++ b/sqlx-core/src/odbc/connection/odbc_bridge.rs @@ -139,13 +139,26 @@ where } } -fn decode_column_name(name_words: Vec, index: u16) -> String { - match String::from_utf16(&name_words) { - Ok(s) => s, - Err(_) => format!("col{}", index - 1), +trait ColumnNameDecode { + fn decode_or_default(self, index: u16) -> String; +} + +impl ColumnNameDecode for Vec { + fn decode_or_default(self, index: u16) -> String { + String::from_utf8(self).unwrap_or_else(|_| format!("col{}", index - 1)) } } +impl ColumnNameDecode for Vec { + fn decode_or_default(self, index: u16) -> String { + String::from_utf16(&self).unwrap_or_else(|_| format!("col{}", index - 1)) + } +} + +fn decode_column_name(name: T, index: u16) -> String { + name.decode_or_default(index) +} + fn stream_rows(cursor: &mut C, columns: &[OdbcColumn], tx: &ExecuteSender) -> Result where C: Cursor, From e232bc1bae94214e8e40977b44a250a3fff7d119 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 25 Sep 2025 20:37:56 +0000 Subject: [PATCH 03/10] Checkpoint before follow-up message Co-authored-by: contact --- sqlx-core/src/odbc/connection/mod.rs | 4 ++-- sqlx-core/src/odbc/connection/odbc_bridge.rs | 23 +++----------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/sqlx-core/src/odbc/connection/mod.rs b/sqlx-core/src/odbc/connection/mod.rs index df1a52b8a9..8622de15cc 100644 --- a/sqlx-core/src/odbc/connection/mod.rs +++ b/sqlx-core/src/odbc/connection/mod.rs @@ -40,7 +40,7 @@ fn create_column(stmt: &mut PreparedStatement, index: u16) -> OdbcColumn { } } -trait ColumnNameDecode { +pub(super) trait ColumnNameDecode { fn decode_or_default(self, index: u16) -> String; } @@ -56,7 +56,7 @@ impl ColumnNameDecode for Vec { } } -fn decode_column_name(name: T, index: u16) -> String { +pub(super) fn decode_column_name(name: T, index: u16) -> String { name.decode_or_default(index) } diff --git a/sqlx-core/src/odbc/connection/odbc_bridge.rs b/sqlx-core/src/odbc/connection/odbc_bridge.rs index 0646f8c5ef..84b570a1f2 100644 --- a/sqlx-core/src/odbc/connection/odbc_bridge.rs +++ b/sqlx-core/src/odbc/connection/odbc_bridge.rs @@ -1,8 +1,9 @@ -use crate::error::Error; +THIS SHOULD BE A LINTER ERRORuse crate::error::Error; use crate::odbc::{ connection::MaybePrepared, OdbcArgumentValue, OdbcArguments, OdbcColumn, OdbcQueryResult, OdbcRow, OdbcTypeInfo, }; +use crate::odbc::connection::decode_column_name; use either::Either; use flume::{SendError, Sender}; use odbc_api::handles::{AsStatementRef, Statement}; @@ -139,25 +140,7 @@ where } } -trait ColumnNameDecode { - fn decode_or_default(self, index: u16) -> String; -} - -impl ColumnNameDecode for Vec { - fn decode_or_default(self, index: u16) -> String { - String::from_utf8(self).unwrap_or_else(|_| format!("col{}", index - 1)) - } -} - -impl ColumnNameDecode for Vec { - fn decode_or_default(self, index: u16) -> String { - String::from_utf16(&self).unwrap_or_else(|_| format!("col{}", index - 1)) - } -} - -fn decode_column_name(name: T, index: u16) -> String { - name.decode_or_default(index) -} +// decode_column_name is provided by connection/mod.rs fn stream_rows(cursor: &mut C, columns: &[OdbcColumn], tx: &ExecuteSender) -> Result where From 31dc4a650adfebe1a68536fd42a0146433f7c35d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 25 Sep 2025 20:38:48 +0000 Subject: [PATCH 04/10] Fix: Remove unused linter directive Co-authored-by: contact --- sqlx-core/src/odbc/connection/odbc_bridge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/odbc/connection/odbc_bridge.rs b/sqlx-core/src/odbc/connection/odbc_bridge.rs index 84b570a1f2..55b08a22b0 100644 --- a/sqlx-core/src/odbc/connection/odbc_bridge.rs +++ b/sqlx-core/src/odbc/connection/odbc_bridge.rs @@ -1,4 +1,4 @@ -THIS SHOULD BE A LINTER ERRORuse crate::error::Error; +use crate::error::Error; use crate::odbc::{ connection::MaybePrepared, OdbcArgumentValue, OdbcArguments, OdbcColumn, OdbcQueryResult, OdbcRow, OdbcTypeInfo, From 4a5f431241cac9b34b9c045db21afae44d546181 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 25 Sep 2025 20:41:56 +0000 Subject: [PATCH 05/10] Fix: Update CI and odbc bridge path Co-authored-by: contact --- .github/workflows/sqlx.yml | 14 ++++++++++++++ sqlx-core/src/odbc/connection/odbc_bridge.rs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index 9bfd80d6aa..28fc73b1e8 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -347,3 +347,17 @@ jobs: run: cargo test --no-default-features --features any,odbc,macros,all-types,runtime-tokio-rustls env: DATABASE_URL: Driver={SQLite3};Database=./tests/odbc/sqlite.db + + windows-build: + name: Windows Build (sqlx-core) + runs-on: windows-latest + needs: check + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + prefix-key: v1-sqlx + save-if: ${{ false }} + - name: Build core with ODBC (Windows) + run: | + cargo build --manifest-path sqlx-core/Cargo.toml --no-default-features --features odbc,all-types,runtime-tokio-rustls diff --git a/sqlx-core/src/odbc/connection/odbc_bridge.rs b/sqlx-core/src/odbc/connection/odbc_bridge.rs index 55b08a22b0..3b87c6cf91 100644 --- a/sqlx-core/src/odbc/connection/odbc_bridge.rs +++ b/sqlx-core/src/odbc/connection/odbc_bridge.rs @@ -3,7 +3,7 @@ use crate::odbc::{ connection::MaybePrepared, OdbcArgumentValue, OdbcArguments, OdbcColumn, OdbcQueryResult, OdbcRow, OdbcTypeInfo, }; -use crate::odbc::connection::decode_column_name; +use super::decode_column_name; use either::Either; use flume::{SendError, Sender}; use odbc_api::handles::{AsStatementRef, Statement}; From 4bc052f274182c92b50e7934766c29277845cf7a Mon Sep 17 00:00:00 2001 From: lovasoa Date: Fri, 26 Sep 2025 00:23:17 +0200 Subject: [PATCH 06/10] Enhance CI configuration for unit tests by adding OS matrix support and removing the Windows build job --- .github/workflows/sqlx.yml | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index 28fc73b1e8..4f0094d74c 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -48,8 +48,11 @@ jobs: -- -D warnings test: - name: Unit Test - runs-on: ubuntu-24.04 + name: Unit Test ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 @@ -57,6 +60,7 @@ jobs: prefix-key: v1-sqlx save-if: ${{ false }} - run: sudo apt-get update && sudo apt-get install -y libodbc2 unixodbc-dev + if: ${{ matrix.os == 'ubuntu-latest' }} - run: cargo test --manifest-path sqlx-core/Cargo.toml --features offline,all-databases,all-types,runtime-tokio-rustls @@ -347,17 +351,3 @@ jobs: run: cargo test --no-default-features --features any,odbc,macros,all-types,runtime-tokio-rustls env: DATABASE_URL: Driver={SQLite3};Database=./tests/odbc/sqlite.db - - windows-build: - name: Windows Build (sqlx-core) - runs-on: windows-latest - needs: check - steps: - - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: v1-sqlx - save-if: ${{ false }} - - name: Build core with ODBC (Windows) - run: | - cargo build --manifest-path sqlx-core/Cargo.toml --no-default-features --features odbc,all-types,runtime-tokio-rustls From afe4a43da815e77e34785fe58f3ab9e10d6961fc Mon Sep 17 00:00:00 2001 From: lovasoa Date: Fri, 26 Sep 2025 00:24:21 +0200 Subject: [PATCH 07/10] Refactor: Remove redundant comment about decode_column_name in odbc_bridge.rs --- sqlx-core/src/odbc/connection/odbc_bridge.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sqlx-core/src/odbc/connection/odbc_bridge.rs b/sqlx-core/src/odbc/connection/odbc_bridge.rs index 3b87c6cf91..01291ea2e9 100644 --- a/sqlx-core/src/odbc/connection/odbc_bridge.rs +++ b/sqlx-core/src/odbc/connection/odbc_bridge.rs @@ -1,9 +1,9 @@ +use super::decode_column_name; use crate::error::Error; use crate::odbc::{ connection::MaybePrepared, OdbcArgumentValue, OdbcArguments, OdbcColumn, OdbcQueryResult, OdbcRow, OdbcTypeInfo, }; -use super::decode_column_name; use either::Either; use flume::{SendError, Sender}; use odbc_api::handles::{AsStatementRef, Statement}; @@ -140,8 +140,6 @@ where } } -// decode_column_name is provided by connection/mod.rs - fn stream_rows(cursor: &mut C, columns: &[OdbcColumn], tx: &ExecuteSender) -> Result where C: Cursor, From 8453bfa13e1431107657a32b9cfc7e74bcce1ed1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Sep 2025 06:45:47 +0000 Subject: [PATCH 08/10] Fix: Normalize SQLite test paths to use forward slashes Co-authored-by: contact --- sqlx-core/src/sqlite/testing/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sqlx-core/src/sqlite/testing/mod.rs b/sqlx-core/src/sqlite/testing/mod.rs index f3e48e6b7c..3360758083 100644 --- a/sqlx-core/src/sqlite/testing/mod.rs +++ b/sqlx-core/src/sqlite/testing/mod.rs @@ -68,9 +68,11 @@ fn convert_path(test_path: &str) -> String { path.set_extension("sqlite"); - path.into_os_string() + let s = path + .into_os_string() .into_string() - .expect("path should be UTF-8") + .expect("path should be UTF-8"); + s.replace('\\', '/') } #[test] From 1ccdaf362fcb59c46897baa895d95110d252f1c0 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Fri, 26 Sep 2025 10:46:19 +0200 Subject: [PATCH 09/10] Fix: Normalize error message case in prepare statement test --- tests/odbc/odbc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/odbc/odbc.rs b/tests/odbc/odbc.rs index c22dbc054e..6ef7323085 100644 --- a/tests/odbc/odbc.rs +++ b/tests/odbc/odbc.rs @@ -794,7 +794,7 @@ async fn it_handles_prepare_statement_errors() -> anyhow::Result<()> { }, Err(sqlx_oldapi::Error::Database(err)) => { assert!( - err.to_string().contains("idonotexist"), + err.to_string().to_lowercase().contains("idonotexist"), "{:?} should contain 'idonotexist'", err ); From 4135031ea3526b291acf49f39985fa894aa990aa Mon Sep 17 00:00:00 2001 From: lovasoa Date: Fri, 26 Sep 2025 10:48:02 +0200 Subject: [PATCH 10/10] Fix: Correct string replacement syntax for path normalization in SQLite tests --- sqlx-core/src/sqlite/testing/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/sqlite/testing/mod.rs b/sqlx-core/src/sqlite/testing/mod.rs index 3360758083..74560f2818 100644 --- a/sqlx-core/src/sqlite/testing/mod.rs +++ b/sqlx-core/src/sqlite/testing/mod.rs @@ -72,7 +72,7 @@ fn convert_path(test_path: &str) -> String { .into_os_string() .into_string() .expect("path should be UTF-8"); - s.replace('\\', '/') + s.replace("\\", "/") } #[test]