Skip to content

Latest commit

 

History

850 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

rust-mssql-driver

An async Microsoft SQL Server driver for Rust.

Crates.io Documentation CI License MSRV

  • 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 streamingquery_stream reads rows from the socket on demand (peak memory ~one row); query_stream_blob sub-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

Installation

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.

Quick Start

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(())
}

Connection Strings

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.

Connection Pooling

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(())
}

Transactions

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(())
}

Derive Macros

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);
}

Authentication

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 Flags

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)

Authentication Features (mssql-auth crate)

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

SQL Server Compatibility

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.

Status

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_executesql by 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.

Scope

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.

Examples

See the examples/ directory:

Documentation

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.

Crate-Specific Documentation

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

Contributing

Contributions are welcome! A few quick pointers:

Community

License

Licensed under either of:

at your option.

Development

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.

Acknowledgments

This project builds on learnings from tiberius and the MS-TDS protocol specification.

About

Async SQL Server driver for Rust — TDS 8.0, built-in pooling, type-state safety, Tokio-native

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

24 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages