An async Microsoft SQL Server driver for Rust.
- TDS 7.3 – 8.0 — SQL Server 2008 through 2022+ strict encryption, plus Azure SQL
- Tokio-native — async from the ground up, no compatibility layers
- Built-in connection pooling — no external pooling crate required
- Type-state connections — invalid operations are compile errors
- Pure-Rust TLS — rustls; no OpenSSL, no system dependencies
- Incremental streaming —
query_streamreads rows from the socket on demand (peak memory ~one row);query_stream_blobsub-streams a multi-GB MAX/BLOB column without buffering it - Beyond queries — transactions and savepoints, stored procedures with OUTPUT params, table-valued parameters, bulk insert, Always Encrypted (read + write), OpenTelemetry
Add to your Cargo.toml:
[dependencies]
mssql-client = "0.20"
tokio = { version = "1.48", features = ["full"] }Windows note: The default TLS feature requires a C compiler (ring/aws-lc-sys). Install Visual Studio Build Tools with the "Desktop development with C++" workload — a one-time setup; see CONTRIBUTING.md for details.
use mssql_client::{Client, Config};
#[tokio::main]
async fn main() -> Result<(), mssql_client::Error> {
// Connect using a connection string
let config = Config::from_connection_string(
"Server=localhost;Database=mydb;User Id=sa;Password=Password123!;TrustServerCertificate=true"
)?;
let mut client = Client::connect(config).await?;
// Execute a query
let rows = client.query("SELECT id, name FROM users WHERE active = @p1", &[&true]).await?;
for result in rows {
let row = result?;
let id: i32 = row.get(0)?;
let name: String = row.get(1)?;
println!("{}: {}", id, name);
}
client.close().await?;
Ok(())
}ADO.NET-style connection strings are supported, including the quoting rules
and the keywords you'd expect (Server, Database, User Id, Password,
Encrypt, TrustServerCertificate, timeouts, Application Name, and more):
Server=hostname,port;Database=dbname;User Id=user;Password=pass;Encrypt=strict;
The full keyword reference lives in the
mssql-client config docs.
Use the built-in connection pool for production applications:
use mssql_driver_pool::Pool;
use mssql_client::Config;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::from_connection_string("...")?;
let pool = Pool::builder()
.client_config(config)
.max_connections(10)
.min_connections(2)
.build()
.await?;
// Get a connection from the pool
let mut conn = pool.get().await?;
let rows = conn.query("SELECT 1", &[]).await?;
// Connection returned to pool when dropped
Ok(())
}use mssql_client::{Client, Config, IsolationLevel};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::from_connection_string("Server=localhost;Database=mydb;User Id=sa;Password=Password123!")?;
let client = Client::connect(config).await?;
let mut tx = client.begin_transaction_with_isolation(IsolationLevel::Serializable).await?;
tx.execute("UPDATE accounts SET balance = balance - 100 WHERE id = @p1", &[&1i32]).await?;
// Savepoints let you roll back part of a transaction
let sp = tx.save_point("transfer").await?;
tx.execute("UPDATE accounts SET balance = balance + 100 WHERE id = @p1", &[&2i32]).await?;
tx.rollback_to(&sp).await?;
// Commit (returns the client)
tx.commit().await?;
Ok(())
}Map rows to structs automatically:
use mssql_derive::FromRow;
#[derive(FromRow)]
struct User {
id: i32,
#[mssql(rename = "user_name")]
name: String,
#[mssql(default)]
email: Option<String>,
}
let rows = client.query("SELECT id, user_name, email FROM users", &[]).await?;
for result in rows {
let user: User = result?.try_into()?;
println!("{}: {}", user.id, user.name);
}
SQL authentication works out of the box. Azure AD / Entra logins use the TDS FEDAUTH SecurityToken workflow and require an encrypted connection. Every method below is validated against live Azure SQL:
| Method | How | Feature |
|---|---|---|
| SQL Server | User Id / Password, or Credentials::sql_server(...) |
default |
| Pre-acquired Entra token | Credentials::azure_token(...) |
default |
| Service Principal | Authentication=ActiveDirectoryServicePrincipal (User Id=<client-id>@<tenant-id>, Password=<secret>) |
azure-identity |
| Managed Identity | Authentication=ActiveDirectoryManagedIdentity |
azure-identity |
| Default chain | Authentication=ActiveDirectoryDefault — managed identity → signed-in az/azd CLI |
azure-identity |
| Certificate | programmatic only: Credentials::certificate(tenant, client, cert_path, password) (X.509 → Entra; not TDS mutual TLS) |
cert-auth |
use mssql_client::{Client, Config};
#[tokio::main]
async fn main() -> Result<(), mssql_client::Error> {
// Managed identity, via a connection string:
let config = Config::from_connection_string(
"Server=myserver.database.windows.net;Database=mydb;\
Authentication=ActiveDirectoryManagedIdentity;Encrypt=mandatory",
)?;
let mut client = Client::connect(config).await?;
client.close().await?;
Ok(())
}The interactive Entra flows (ActiveDirectoryPassword / Interactive /
DeviceCodeFlow) are not built in — azure_identity ships no such
credentials. Acquire the token yourself (MSAL, the oauth2 crate,
az account get-access-token, or any broker) and pass it via
Credentials::azure_token, exactly like .NET's SqlConnection.AccessToken.
| Feature | Default | Description |
|---|---|---|
chrono |
Yes | Date/time type support via chrono |
uuid |
Yes | UUID type support |
decimal |
Yes | Decimal type support via rust_decimal |
encoding |
Yes | Collation-aware VARCHAR decoding |
json |
No | JSON type support via serde_json |
tls |
Yes | TLS/SSL encryption via rustls (disable for Encrypt=no_tls environments) |
azure-identity |
No | Azure AD / Entra logins via Managed Identity, Service Principal, or the default credential chain (pre-acquired tokens work without it) |
otel |
No | OpenTelemetry tracing and metrics |
zeroize |
No | Secure credential wiping |
filestream |
No | FILESTREAM BLOB access (Windows only, requires OLE DB Driver) |
| Feature | Description |
|---|---|
azure-identity |
Azure Managed Identity and Service Principal |
integrated-auth |
Kerberos/GSSAPI (Linux/macOS) |
sspi-auth |
Windows SSPI (cross-platform via sspi-rs) |
cert-auth |
Client certificate authentication |
zeroize |
Secure credential wiping from memory |
always-encrypted |
Transparent column decryption with Azure Key Vault and Windows CertStore key providers |
Enable optional features:
cargo add mssql-client --features otel
cargo add mssql-auth --features sspi-auth| Version | Notes |
|---|---|
| SQL Server 2008 – 2016 | TDS 7.3/7.4. These servers often lack TLS 1.2 (rustls requires it); use Encrypt=no_tls on a trusted network |
| SQL Server 2017 – 2019 | TDS 7.4, full TLS |
| SQL Server 2022+ | TDS 7.4 or 8.0 strict mode |
| Azure SQL Database / Managed Instance | Including automatic gateway redirects |
How each version is validated: SQL Server 2017, 2019, and 2022 are CI-verified — the integration suite runs the full ignored test suite against all three on every change. SQL Server 2008–2016 and Azure SQL are validated manually against real servers, not in CI.
Known quirks: SQL Server 2014 RTM reports ProductMajorVersion as NULL (the
driver falls back to parsing ProductVersion), and legacy servers negotiating
TLS 1.0/1.1 fail with "TLS handshake eof" under Encrypt=true — use
Encrypt=no_tls there. See LIMITATIONS.md for the rest.
Pre-1.0 and actively maintained — by a single maintainer on a best-effort basis (see MAINTAINERS.md), with the aim of a first response to issues and pull requests within about a week. The API may still change between 0.x minors; STABILITY.md describes what is already considered settled and the road to 1.0. An integration suite runs against a real SQL Server in CI on every change.
Known gaps, so you don't have to discover them yourself:
- Kerberos/GSSAPI and FILESTREAM are implemented but not yet validated against live infrastructure.
- Always Encrypted reads are fully transparent; writes cover the full scalar, temporal, and fixed-width type set (see LIMITATIONS.md for the exact list and constraints).
- Parameterized queries run via
sp_executesqlby default (the server still reuses plans); a client-side prepared-statement cache is available opt-in (Statement Cache=true). - No MARS (multiple active result sets on one connection).
The full list, with workarounds where they exist, is in LIMITATIONS.md.
This driver speaks TDS natively in pure Rust — no ODBC driver manager, no OpenSSL, no C toolchain (outside the optional Windows SSPI feature) — and is Tokio-only by design. It does not do compile-time query checking; if you want sqlx-style checked queries, this is not that. Coming from Tiberius? MIGRATION.md maps the API differences.
See the examples/ directory:
basic.rs- Connection and queriestransactions.rs- Transaction handlingstreaming.rs- Incremental streaming of large result sets and BLOBs (query_stream/query_stream_blob)bulk_insert.rs- Bulk data loadingderive_macros.rs- Row mapping macros
- API Documentation - Full API reference on docs.rs
- ARCHITECTURE.md - Design decisions, ADRs, and internals
- CHANGELOG.md - Version history and release notes
- STABILITY.md - API stability guarantees and versioning policy
- SECURITY.md - Security policy, threat model, and best practices
- LIMITATIONS.md - Known limitations and explicit non-goals
- MIGRATION.md - Migrating from Tiberius
Feature and usage guides — connection strings, stored procedures, DDL, LOBs, cancellation, Always Encrypted, FILESTREAM, OpenTelemetry, error handling, pool metrics, and TLS — live in the crate rustdoc on docs.rs; see the relevant module on each crate's page.
Each crate has its own README with crate-specific documentation:
| Crate | Description |
|---|---|
mssql-client |
Main client API |
mssql-driver-pool |
Connection pooling |
mssql-derive |
Derive macros |
mssql-types |
Type conversions |
mssql-auth |
Authentication providers |
mssql-tls |
TLS negotiation |
tds-protocol |
TDS protocol layer |
mssql-codec |
Async framing |
mssql-testing |
Test infrastructure |
Contributions are welcome! A few quick pointers:
- First time? Read CONTRIBUTING.md § First Contribution for the shortest path from clone to green CI.
- Filing an issue? Use the issue templates — they'll ask the right questions so reviewers can help faster.
- Opening a PR? The PR template walks you through what reviewers need to know.
- Architecture changes? Review ARCHITECTURE.md and the ADR process.
- Code of Conduct: We follow the Rust Code of Conduct.
- Current maintainers and how to contact them: MAINTAINERS.md.
- Questions and discussions: GitHub Discussions
- Bugs and feature requests: GitHub Issues
- Security vulnerabilities: Private Security Advisory — see SECURITY.md
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
This project is built with heavy AI assistance, with a human maintainer reviewing and accountable for every change. The public API is documented on docs.rs, the protocol layer has unit and property tests, and an integration suite runs against a real SQL Server in CI on every change. Known gaps are documented in LIMITATIONS.md; if something doesn't hold up, please open an issue.
This project builds on learnings from tiberius and the MS-TDS protocol specification.