Skip to content

Commit d91972c

Browse files
authored
Merge pull request #16 from pabl-o-ce/fix/mssql-rows-affected
fix(mssql): report real rows_affected for parameterized DML
2 parents c14c499 + 36baa59 commit d91972c

34 files changed

Lines changed: 893 additions & 313 deletions

.github/workflows/mssql.yml

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
name: MSSQL CI
2+
3+
# Dedicated CI for this MSSQL-focused fork of SQLx.
4+
# It mirrors the MSSQL-relevant parts of `sqlx.yml` but scoped to MSSQL features
5+
# only, so the status of the MSSQL driver changes is easy to see at a glance.
6+
# The upstream `sqlx.yml` / `sqlx-cli.yml` / `examples.yml` workflows are left
7+
# untouched.
8+
9+
on:
10+
pull_request:
11+
push:
12+
branches:
13+
- main
14+
- "*-dev"
15+
16+
jobs:
17+
format:
18+
name: Format
19+
runs-on: ubuntu-24.04
20+
timeout-minutes: 15
21+
steps:
22+
- uses: actions/checkout@v5
23+
- run: rustup component add rustfmt
24+
- run: cargo fmt --all -- --check
25+
26+
check:
27+
name: Check (MSSQL)
28+
runs-on: ubuntu-24.04
29+
strategy:
30+
matrix:
31+
runtime: [ tokio ]
32+
tls: [ native-tls, rustls, none ]
33+
timeout-minutes: 30
34+
steps:
35+
- uses: actions/checkout@v5
36+
37+
# Swatinem/rust-cache recommends setting up the rust toolchain first
38+
# because it's used in the cache keys.
39+
- name: Setup Rust
40+
run: |
41+
rustup show active-toolchain || rustup toolchain install
42+
rustup component add clippy
43+
44+
- uses: Swatinem/rust-cache@v2
45+
46+
- run: >
47+
cargo clippy
48+
--no-default-features
49+
--features mssql,any,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }},macros,migrate
50+
-- -D warnings
51+
52+
test:
53+
name: Unit Tests (MSSQL)
54+
runs-on: ubuntu-24.04
55+
timeout-minutes: 30
56+
steps:
57+
- uses: actions/checkout@v5
58+
59+
# `sqlx-mssql --all-features` enables tiberius `integrated-auth-gssapi`,
60+
# which builds `libgssapi-sys` and needs the Kerberos dev headers.
61+
- name: Install Kerberos headers (for tiberius integrated-auth-gssapi)
62+
run: sudo apt-get update && sudo apt-get install -y libkrb5-dev
63+
64+
- name: Setup Rust
65+
run: rustup show active-toolchain || rustup toolchain install
66+
67+
- uses: Swatinem/rust-cache@v2
68+
69+
- name: Test sqlx-core
70+
run: cargo test -p sqlx-core --all-features
71+
72+
- name: Test sqlx-mssql
73+
run: cargo test -p sqlx-mssql --all-features
74+
75+
# Note: use `--lib` to not run integration tests that require a DB.
76+
- name: Test sqlx (lib only)
77+
run: >
78+
cargo test
79+
-p sqlx
80+
--lib
81+
--no-default-features
82+
--features mssql,any,_unstable-all-types,macros,migrate,runtime-tokio,tls-rustls
83+
84+
mssql:
85+
name: MSSQL
86+
runs-on: ubuntu-24.04
87+
strategy:
88+
matrix:
89+
mssql: [ 2022, 2019 ]
90+
runtime: [ tokio ]
91+
tls: [ rustls-ring, none ]
92+
needs: check
93+
timeout-minutes: 30
94+
steps:
95+
- uses: actions/checkout@v5
96+
97+
- name: Setup Rust
98+
run: rustup show active-toolchain || rustup toolchain install
99+
100+
- uses: Swatinem/rust-cache@v2
101+
102+
- run: >
103+
cargo build
104+
--no-default-features
105+
--features mssql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }},macros,migrate
106+
107+
# Retry: the SQL Server base image pull from mcr.microsoft.com can return a
108+
# transient 403 when many matrix jobs pull it concurrently.
109+
- name: Start SQL Server
110+
run: |
111+
for i in 1 2 3; do
112+
docker rm -f mssql_${{ matrix.mssql }} 2>/dev/null || true
113+
if docker compose -f tests/docker-compose.yml run -d -p 1433:1433 --name mssql_${{ matrix.mssql }} mssql_${{ matrix.mssql }}; then
114+
exit 0
115+
fi
116+
echo "::warning::SQL Server start attempt $i failed (transient registry error?); retrying in 20s"
117+
sleep 20
118+
done
119+
echo "::error::Failed to start SQL Server after 3 attempts"
120+
exit 1
121+
- run: sleep 60
122+
123+
# Create data dir for offline mode
124+
- run: mkdir .sqlx
125+
126+
- run: >
127+
cargo test
128+
--no-default-features
129+
--features any,mssql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
130+
env:
131+
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx?sslmode=disabled
132+
SQLX_OFFLINE_DIR: .sqlx
133+
RUSTFLAGS: --cfg mssql_${{ matrix.mssql }}
134+
135+
# Run the `test-attr` test again to cover cleanup.
136+
- run: >
137+
cargo test
138+
--test mssql-test-attr
139+
--no-default-features
140+
--features any,mssql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
141+
env:
142+
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx?sslmode=disabled
143+
SQLX_OFFLINE_DIR: .sqlx
144+
RUSTFLAGS: --cfg mssql_${{ matrix.mssql }}
145+
146+
# Remove test artifacts
147+
- run: cargo clean -p sqlx
148+
149+
# Build the macros-test in offline mode (omit DATABASE_URL)
150+
- run: >
151+
cargo build
152+
--no-default-features
153+
--test mssql-macros
154+
--features any,mssql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
155+
env:
156+
SQLX_OFFLINE: true
157+
SQLX_OFFLINE_DIR: .sqlx
158+
RUSTFLAGS: -D warnings --cfg mssql_${{ matrix.mssql }}
159+
160+
# Test macros in offline mode (still needs DATABASE_URL to run)
161+
- run: >
162+
cargo test
163+
--no-default-features
164+
--test mssql-macros
165+
--features any,mssql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
166+
env:
167+
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx?sslmode=disabled
168+
SQLX_OFFLINE: true
169+
SQLX_OFFLINE_DIR: .sqlx
170+
RUSTFLAGS: --cfg mssql_${{ matrix.mssql }}

.github/workflows/sqlx.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ jobs:
8787
steps:
8888
- uses: actions/checkout@v5
8989

90+
# `sqlx-mssql --all-features` enables tiberius `integrated-auth-gssapi`,
91+
# which builds `libgssapi-sys` and needs the Kerberos dev headers.
92+
- name: Install Kerberos headers (for tiberius integrated-auth-gssapi)
93+
run: sudo apt-get update && sudo apt-get install -y libkrb5-dev
94+
9095
# https://blog.rust-lang.org/2025/03/02/Rustup-1.28.0.html
9196
- name: Setup Rust
9297
run: rustup show active-toolchain || rustup toolchain install
@@ -746,7 +751,20 @@ jobs:
746751
--no-default-features
747752
--features mssql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }},macros,migrate
748753
749-
- run: docker compose -f tests/docker-compose.yml run -d -p 1433:1433 --name mssql_${{ matrix.mssql }} mssql_${{ matrix.mssql }}
754+
# Retry: the SQL Server base image pull from mcr.microsoft.com can return a
755+
# transient 403 when many matrix jobs pull it concurrently.
756+
- name: Start SQL Server
757+
run: |
758+
for i in 1 2 3; do
759+
docker rm -f mssql_${{ matrix.mssql }} 2>/dev/null || true
760+
if docker compose -f tests/docker-compose.yml run -d -p 1433:1433 --name mssql_${{ matrix.mssql }} mssql_${{ matrix.mssql }}; then
761+
exit 0
762+
fi
763+
echo "::warning::SQL Server start attempt $i failed (transient registry error?); retrying in 20s"
764+
sleep 20
765+
done
766+
echo "::error::Failed to start SQL Server after 3 attempts"
767+
exit 1
750768
- run: sleep 60
751769

752770
# Create data dir for offline mode
@@ -757,7 +775,7 @@ jobs:
757775
--no-default-features
758776
--features any,mssql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
759777
env:
760-
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx
778+
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx?sslmode=disabled
761779
SQLX_OFFLINE_DIR: .sqlx
762780
RUSTFLAGS: --cfg mssql_${{ matrix.mssql }}
763781
@@ -768,7 +786,7 @@ jobs:
768786
--no-default-features
769787
--features any,mssql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
770788
env:
771-
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx
789+
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx?sslmode=disabled
772790
SQLX_OFFLINE_DIR: .sqlx
773791
RUSTFLAGS: --cfg mssql_${{ matrix.mssql }}
774792
@@ -793,7 +811,7 @@ jobs:
793811
--test mssql-macros
794812
--features any,mssql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
795813
env:
796-
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx
814+
DATABASE_URL: mssql://sa:YourStrong!Passw0rd@localhost:1433/sqlx?sslmode=disabled
797815
SQLX_OFFLINE: true
798816
SQLX_OFFLINE_DIR: .sqlx
799817
RUSTFLAGS: --cfg mssql_${{ matrix.mssql }}

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
<!-- Github Actions -->
1212
<a href="https://github.com/launchbadge/sqlx/actions/workflows/sqlx.yml?query=branch%3Amain">
1313
<img src="https://img.shields.io/github/actions/workflow/status/launchbadge/sqlx/sqlx.yml?branch=main&style=flat-square" alt="actions status" /></a>
14+
<!-- MSSQL fork CI (this fork) -->
15+
<a href="https://github.com/pabl-o-ce/sqlx/actions/workflows/mssql.yml?query=branch%3Amain">
16+
<img src="https://img.shields.io/github/actions/workflow/status/pabl-o-ce/sqlx/mssql.yml?branch=main&style=flat-square&label=mssql%20ci" alt="MSSQL CI status" /></a>
1417
<!-- Version -->
1518
<a href="https://crates.io/crates/sqlx">
1619
<img src="https://img.shields.io/crates/v/sqlx.svg?style=flat-square"
@@ -27,6 +30,14 @@
2730
</a>
2831
</div>
2932

33+
<br />
34+
35+
> **Fork note:** This is an **MSSQL (SQL Server)-focused fork** of SQLx. The
36+
> **`mssql ci`** badge above tracks the dedicated
37+
> [`MSSQL CI`](.github/workflows/mssql.yml) workflow — watch it to know whether
38+
> the MSSQL driver changes are passing. The upstream multi-database workflows
39+
> are left as-is.
40+
3041
<div align="center">
3142
<h4>
3243
<a href="#install">

benches/sqlite/describe.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1+
// This single-threaded benchmark deliberately holds a `RefCell` borrow across
2+
// `.await` (the connection is shared only within one task).
3+
#![allow(clippy::await_holding_refcell_ref)]
4+
15
use criterion::BenchmarkId;
26
use criterion::Criterion;
37
use criterion::{criterion_group, criterion_main};
48

59
use sqlx::sqlite::{Sqlite, SqliteConnection};
6-
use sqlx::Executor;
10+
use sqlx::{Executor, SqlSafeStr};
711
use sqlx_test::new;
812

913
// Here we have an async function to benchmark
1014
async fn do_describe_trivial(db: &std::cell::RefCell<SqliteConnection>) {
11-
db.borrow_mut().describe("select 1").await.unwrap();
15+
db.borrow_mut()
16+
.describe("select 1".into_sql_str())
17+
.await
18+
.unwrap();
1219
}
1320

1421
async fn do_describe_recursive(db: &std::cell::RefCell<SqliteConnection>) {
@@ -27,22 +34,23 @@ async fn do_describe_recursive(db: &std::cell::RefCell<SqliteConnection>) {
2734
begin_date
2835
FROM schedule
2936
GROUP BY begin_date
30-
"#,
37+
"#
38+
.into_sql_str(),
3139
)
3240
.await
3341
.unwrap();
3442
}
3543

3644
async fn do_describe_insert(db: &std::cell::RefCell<SqliteConnection>) {
3745
db.borrow_mut()
38-
.describe("INSERT INTO tweet (id, text) VALUES (2, 'Hello') RETURNING *")
46+
.describe("INSERT INTO tweet (id, text) VALUES (2, 'Hello') RETURNING *".into_sql_str())
3947
.await
4048
.unwrap();
4149
}
4250

4351
async fn do_describe_insert_fks(db: &std::cell::RefCell<SqliteConnection>) {
4452
db.borrow_mut()
45-
.describe("insert into statements (text) values ('a') returning id")
53+
.describe("insert into statements (text) values ('a') returning id".into_sql_str())
4654
.await
4755
.unwrap();
4856
}

examples/mssql/todos/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ edition = "2021"
55
workspace = "../../../"
66

77
[dependencies]
8-
anyhow = "1.0"
8+
anyhow = "1.0.58"
99
sqlx = { path = "../../../", features = [ "mssql", "runtime-tokio", "tls-native-tls" ] }
10-
clap = { version = "4", features = ["derive"] }
11-
tokio = { version = "1.20.0", features = ["rt", "macros"] }
12-
dotenvy = "0.15.0"
10+
clap = { version = "4.4.7", features = ["derive"] }
11+
tokio = { version = "1.25.0", features = ["rt", "macros"] }
12+
dotenvy = "0.15.7"

sqlx-mssql/Cargo.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ uuid = ["dep:uuid", "sqlx-core/uuid"]
3030
sqlx-core = { workspace = true }
3131

3232
# TDS protocol driver
33-
tiberius = { version = "0.12", default-features = false, features = ["tds73"] }
33+
tiberius = { version = "0.12.3", default-features = false, features = ["tds73"] }
3434

3535
# Futures crates
36-
futures-core = { version = "0.3.19", default-features = false }
37-
futures-io = "0.3.24"
38-
futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] }
36+
futures-core = { version = "0.3.32", default-features = false }
37+
futures-io = "0.3.32"
38+
futures-util = { version = "0.3.32", default-features = false, features = ["alloc", "sink", "io"] }
3939

4040
# Runtime bridging
4141
tokio = { workspace = true, optional = true }
@@ -50,16 +50,16 @@ time = { workspace = true, optional = true }
5050
uuid = { workspace = true, optional = true }
5151

5252
# Misc
53-
bytes = "1.1.0"
53+
bytes = "1.2.0"
5454
either = "1.6.1"
5555
log = "0.4.18"
5656
tracing = { version = "0.1.37", features = ["log"] }
57-
percent-encoding = "2.1.0"
57+
percent-encoding = "2.3.0"
5858

5959
dotenvy.workspace = true
6060
thiserror.workspace = true
6161

62-
serde = { version = "1.0.144", optional = true }
62+
serde = { version = "1.0.219", optional = true }
6363

6464
[dev-dependencies]
6565
# FIXME: https://github.com/rust-lang/cargo/issues/15622

sqlx-mssql/src/advisory_lock.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,16 @@ impl MssqlAdvisoryLock {
196196
/// Returns `Ok(true)` if the lock was successfully released, `Ok(false)`
197197
/// if the lock was not held by this session.
198198
pub async fn release(&self, conn: &mut MssqlConnection) -> Result<bool, Error> {
199+
// `sp_releaseapplock` *raises* error 1223 (rather than returning a
200+
// status) when the lock isn't held by this session; catch it and map it
201+
// to the -999 sentinel so a not-held release reports `Ok(false)`.
199202
let sql = "DECLARE @r INT; \
200-
EXEC @r = sp_releaseapplock @Resource = @p1, @LockOwner = 'Session'; \
203+
BEGIN TRY \
204+
EXEC @r = sp_releaseapplock @Resource = @p1, @LockOwner = 'Session'; \
205+
END TRY \
206+
BEGIN CATCH \
207+
IF ERROR_NUMBER() = 1223 SET @r = -999; ELSE THROW; \
208+
END CATCH; \
201209
SELECT @r;";
202210

203211
let status: i32 = query_scalar(sql)

0 commit comments

Comments
 (0)