Skip to main content

sqlx_mssql_odbc/
lib.rs

1//! MSSQL driver for SQLx via ODBC.
2//!
3//! `sqlx-mssql-odbc` connects SQLx to Microsoft SQL Server through an ODBC driver
4//! manager. This is the **facade crate** — it re-exports everything from
5//! [`sqlx-mssql-odbc-core`].
6//!
7//! # Quick start
8//!
9//! ```toml
10//! [dependencies]
11//! sqlx-core = "0.9.0"
12//! sqlx-mssql-odbc = "0.1"
13//! tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
14//! ```
15//!
16//! ```no_run
17//! use sqlx_core::connection::Connection;
18//! use sqlx_core::row::Row;
19//! use sqlx_mssql_odbc::MssqlConnection;
20//!
21//! # async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
22//! let mut conn = MssqlConnection::connect(
23//!     "mssql://user:password@localhost:1433/database"
24//! ).await?;
25//!
26//! let row = sqlx_core::query::query("SELECT 42")
27//!     .fetch_one(&mut conn)
28//!     .await?;
29//!
30//! let value: i32 = row.try_get(0)?;
31//! assert_eq!(value, 42);
32//!
33//! conn.close().await?;
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! `MssqlConnection::connect()` accepts a standard `mssql://` URL, a raw ODBC
39//! connection string, or a bare DSN name.
40//!
41//! # Usage patterns
42//!
43//! ## Connection pooling
44//!
45//! ```no_run
46//! use sqlx_mssql_odbc::MssqlPoolOptions;
47//!
48//! # async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
49//! let pool = MssqlPoolOptions::new()
50//!     .max_connections(10)
51//!     .connect("mssql://user:password@localhost:1433/database")
52//!     .await?;
53//!
54//! let row = sqlx_core::query::query("SELECT 1")
55//!     .fetch_one(&pool)
56//!     .await?;
57//!
58//! pool.close().await;
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! ## Parameterised queries
64//!
65//! ```no_run
66//! use sqlx_core::row::Row;
67//! use sqlx_mssql_odbc::MssqlConnection;
68//! use sqlx_core::connection::Connection;
69//!
70//! # async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
71//! let mut conn = MssqlConnection::connect("mssql://…").await?;
72//!
73//! let row = sqlx_core::query::query("SELECT @p1 + @p2")
74//!     .bind(10i32)
75//!     .bind(20i32)
76//!     .fetch_one(&mut conn)
77//!     .await?;
78//!
79//! let total: i32 = row.try_get(0)?;
80//! assert_eq!(total, 30);
81//! # Ok(())
82//! # }
83//! ```
84//!
85//! ## Compile-time checked queries (`macros` feature)
86//!
87//! Enable the `macros` feature to get compile-time validation of SQL against a
88//! live database:
89//!
90//! ```toml
91//! [dependencies]
92//! sqlx-mssql-odbc = { version = "0.1", features = ["macros"] }
93//! ```
94//!
95//! ```ignore
96//! # use sqlx_mssql_odbc::MssqlConnection;
97//! # use sqlx_core::connection::Connection;
98//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
99//! let mut conn = MssqlConnection::connect("…").await?;
100//!
101//! // The column name and type are checked at compile time.
102//! let row = sqlx_mssql_odbc::query!("SELECT 1 AS one")
103//!     .fetch_one(&mut conn)
104//!     .await?;
105//! assert_eq!(row.one, 1i32);
106//!
107//! // Bind parameters with $1, $2, etc.
108//! let row = sqlx_mssql_odbc::query!("SELECT @p1 + @p2 AS total", 10i32, 20i32)
109//!     .fetch_one(&mut conn)
110//!     .await?;
111//! assert_eq!(row.total, 30);
112//! # Ok(())
113//! # }
114//! ```
115//!
116//! For CI or offline builds, use `cargo sqlx prepare` to cache the schema so
117//! the macros can check queries without a live database.
118//!
119//! ## Derive macros (`derive` feature)
120//!
121//! ```toml
122//! [dependencies]
123//! sqlx-mssql-odbc = { version = "0.1", features = ["derive"] }
124//! ```
125//!
126//! ```ignore
127//! use sqlx_mssql_odbc::FromRow;
128//!
129//! #[derive(Debug, FromRow)]
130//! struct User {
131//!     id: i32,
132//!     name: String,
133//!     email: Option<String>,
134//! }
135//!
136//! # async fn example(mut conn: sqlx_mssql_odbc::MssqlConnection)
137//! #     -> Result<(), Box<dyn std::error::Error>>
138//! # {
139//! let users = sqlx_mssql_odbc::query_as!(User, "SELECT id, name, email FROM users")
140//!     .fetch_all(&mut conn)
141//!     .await?;
142//! # Ok(())
143//! # }
144//! ```
145//!
146//! ## Transactions
147//!
148//! ```no_run
149//! use sqlx_core::connection::Connection;
150//! use sqlx_core::executor::Executor;
151//! use sqlx_mssql_odbc::MssqlConnection;
152//!
153//! # async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
154//! let mut conn = MssqlConnection::connect("mssql://…").await?;
155//!
156//! conn.begin().await?;
157//! sqlx_core::query::query("INSERT INTO users (name) VALUES (@p1)")
158//!     .bind("Alice")
159//!     .execute(&mut conn)
160//!     .await?;
161//! conn.commit().await?;
162//! # Ok(())
163//! # }
164//! ```
165//!
166//! # URL parameters
167//!
168//! | Parameter | Description |
169//! |---|---|
170//! | `encrypt=true` | Enable TLS encryption |
171//! | `trust_certificate=true` | Skip certificate validation |
172//! | `driver=…` | Custom ODBC driver name (default: `ODBC Driver 18 for SQL Server`) |
173//!
174//! # Requirements
175//!
176//! On Linux and macOS you need both a driver manager (unixODBC) and the
177//! Microsoft ODBC Driver for SQL Server (version 17 or 18). See the
178//! [repository README](https://github.com/strawberyy-coconut/sqlx-mssql-odbc)
179//! for platform-specific installation instructions.
180//!
181//! Enable the `vendored-unix-odbc` feature to statically link unixODBC into
182//! your application on Linux or macOS.
183//!
184//! # Features
185//!
186//! | Feature | Description |
187//! |---|---|
188//! | `bigdecimal` | [`BigDecimal`] type support |
189//! | `chrono` | [`chrono`] datetime types |
190//! | `rust_decimal` / `decimal` | [`rust_decimal::Decimal`] support |
191//! | `json` | [`serde_json::Value`] support |
192//! | `time` | [`time`] crate datetime types |
193//! | `uuid` | [`uuid::Uuid`] support |
194//! | `macros` | `query!()`, `query_as!()` and other proc macros |
195//! | `derive` | `Encode`, `Decode`, `Type`, `FromRow` derive macros |
196//! | `offline` | Compile-time query checking with `query!()` |
197//! | `migrate` | Database migration support |
198//! | `runtime-tokio` | Tokio runtime support |
199//! | `tls-none` | No TLS (default) |
200//! | `spatial` | [`geo_types`] spatial type support |
201//! | `vendored-unix-odbc` | Statically link unixODBC |
202
203#![cfg_attr(docsrs, feature(doc_cfg))]
204
205// Re-export everything from sqlx-mssql-odbc-core.
206pub use sqlx_mssql_odbc_core::*;
207
208/// Re-export of the core crate for direct access.
209pub use sqlx_mssql_odbc_core as core;
210
211// ---------------------------------------------------------------------------
212// Macro wrappers — only available with the `macros` feature
213// ---------------------------------------------------------------------------
214
215#[cfg(feature = "macros")]
216#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
217pub use sqlx_mssql_odbc_macros::expand_query;
218
219/// Compile-time checked SQL query for MSSQL via ODBC.
220///
221/// The SQL is sent to a live database at compile time so column names and
222/// types are verified. The returned row lets you access columns as named
223/// fields with correct Rust types — no runtime mapping needed.
224///
225/// # Without parameters
226///
227/// ```ignore
228/// # use sqlx_mssql_odbc::MssqlConnection;
229/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
230/// let mut conn = MssqlConnection::connect("…").await?;
231///
232/// let row = sqlx_mssql_odbc::query!("SELECT id, name, email FROM users WHERE id = 1")
233///     .fetch_one(&mut conn)
234///     .await?;
235///
236/// // Fields are checked at compile time — typos become compile errors!
237/// println!("{} <{}>", row.name, row.email.unwrap_or_default());
238/// # Ok(())
239/// # }
240/// ```
241///
242/// # With parameters
243///
244/// Bind parameters with `@p1`, `@p2`, etc. The macro infers types from the
245/// Rust expressions:
246///
247/// ```ignore
248/// # use sqlx_mssql_odbc::MssqlConnection;
249/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
250/// # let mut conn = MssqlConnection::connect("…").await?;
251/// let user_id = 42;
252/// let rows = sqlx_mssql_odbc::query!(
253///     "SELECT id, name FROM users WHERE id = @p1 OR name LIKE @p2",
254///     user_id,
255///     "%Smith%"
256/// )
257///     .fetch_all(&mut conn)
258///     .await?;
259/// # Ok(())
260/// # }
261/// ```
262///
263/// # Inferring multiple result columns
264///
265/// ```ignore
266/// # use sqlx_mssql_odbc::MssqlConnection;
267/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
268/// # let mut conn = MssqlConnection::connect("…").await?;
269/// let row = sqlx_mssql_odbc::query!("SELECT COUNT(*) AS count, AVG(age) AS avg_age FROM users")
270///     .fetch_one(&mut conn)
271///     .await?;
272/// println!("{} users, average age {}", row.count, row.avg_age);
273/// # Ok(())
274/// # }
275/// ```
276///
277/// Requires the `macros` feature and a live database (or a prepared offline
278/// cache via `cargo sqlx prepare`).
279#[cfg(feature = "macros")]
280#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
281#[macro_export]
282macro_rules! query {
283    ($query:expr) => ({
284        $crate::expand_query!(source = $query)
285    });
286    ($query:expr, $($args:tt)*) => ({
287        $crate::expand_query!(source = $query, args = [$($args)*])
288    });
289}
290
291/// Compile-time checked SQL query (unchecked variant — skips database
292/// verification at compile time).
293///
294/// Like [`query!`] but does not require `DATABASE_URL` or an offline cache.
295/// Column names and types are **not** verified against the database schema;
296/// they are inferred from the query text alone.
297///
298/// Requires the `macros` feature.
299#[cfg(feature = "macros")]
300#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
301#[macro_export]
302macro_rules! query_unchecked {
303    ($query:expr) => ({
304        $crate::expand_query!(source = $query, checked = false)
305    });
306    ($query:expr, $($args:tt)*) => ({
307        $crate::expand_query!(source = $query, args = [$($args)*], checked = false)
308    });
309}
310
311/// Compile-time checked SQL query for MSSQL via ODBC, mapping to a named struct.
312///
313/// Like [`query!`] but deserialises each row into a struct you provide. The
314/// struct fields are matched to columns by name and their types are checked
315/// against the database schema at compile time.
316///
317/// ```ignore
318/// # use sqlx_mssql_odbc::MssqlConnection;
319/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
320/// # let mut conn = MssqlConnection::connect("…").await?;
321/// struct User {
322///     id: i32,
323///     name: String,
324/// }
325///
326/// let users = sqlx_mssql_odbc::query_as!(User, "SELECT id, name FROM users")
327///     .fetch_all(&mut conn)
328///     .await?;
329///
330/// for user in users {
331///     println!("{}: {}", user.id, user.name);
332/// }
333/// # Ok(())
334/// # }
335/// ```
336///
337/// Combine with the `derive` feature's `FromRow` derive to avoid manually
338/// writing struct definitions:
339///
340/// ```ignore
341/// # use sqlx_mssql_odbc::{MssqlConnection, FromRow};
342/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
343/// # let mut conn = MssqlConnection::connect("…").await?;
344/// #[derive(Debug, FromRow)]
345/// struct Order {
346///     id: i32,
347///     total: rust_decimal::Decimal,
348///     placed_at: chrono::NaiveDateTime,
349/// }
350///
351/// let orders = sqlx_mssql_odbc::query_as!(Order, "SELECT id, total, placed_at FROM orders")
352///     .fetch_all(&mut conn)
353///     .await?;
354/// # Ok(())
355/// # }
356/// ```
357///
358/// Requires the `macros` feature.
359#[cfg(feature = "macros")]
360#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
361#[macro_export]
362macro_rules! query_as {
363    ($out_struct:path, $query:expr) => ({
364        $crate::expand_query!(record = $out_struct, source = $query)
365    });
366    ($out_struct:path, $query:expr, $($args:tt)*) => ({
367        $crate::expand_query!(record = $out_struct, source = $query, args = [$($args)*])
368    });
369}
370
371/// Compile-time checked SQL query mapping to a struct (unchecked variant).
372///
373/// Like [`query_as!`] but does **not** verify column names or types against
374/// the database at compile time. Useful for CI/deploy environments that lack
375/// database connectivity.
376///
377/// Requires the `macros` feature.
378#[cfg(feature = "macros")]
379#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
380#[macro_export]
381macro_rules! query_as_unchecked {
382    ($out_struct:path, $query:expr) => ({
383        $crate::expand_query!(record = $out_struct, source = $query, checked = false)
384    });
385    ($out_struct:path, $query:expr, $($args:tt)*) => ({
386        $crate::expand_query!(record = $out_struct, source = $query, args = [$($args)*], checked = false)
387    });
388}
389
390/// Compile-time checked SQL query for MSSQL via ODBC, returning a single scalar
391/// value.
392///
393/// ```ignore
394/// # use sqlx_mssql_odbc::MssqlConnection;
395/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
396/// # let mut conn = MssqlConnection::connect("…").await?;
397/// let count: i64 = sqlx_mssql_odbc::query_scalar!("SELECT COUNT(*) FROM users")
398///     .fetch_one(&mut conn)
399///     .await?;
400///
401/// let max_id: Option<i32> = sqlx_mssql_odbc::query_scalar!("SELECT MAX(id) FROM users")
402///     .fetch_optional(&mut conn)
403///     .await?;
404/// # Ok(())
405/// # }
406/// ```
407///
408/// Requires the `macros` feature.
409#[cfg(feature = "macros")]
410#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
411#[macro_export]
412macro_rules! query_scalar {
413    ($query:expr) => (
414        $crate::expand_query!(scalar = _, source = $query)
415    );
416    ($query:expr, $($args:tt)*) => (
417        $crate::expand_query!(scalar = _, source = $query, args = [$($args)*])
418    );
419}
420
421/// Compile-time checked SQL query returning a single scalar (unchecked variant).
422///
423/// Like [`query_scalar!`] but skips database verification at compile time.
424///
425/// Requires the `macros` feature.
426#[cfg(feature = "macros")]
427#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
428#[macro_export]
429macro_rules! query_scalar_unchecked {
430    ($query:expr) => (
431        $crate::expand_query!(scalar = _, source = $query, checked = false)
432    );
433    ($query:expr, $($args:tt)*) => (
434        $crate::expand_query!(scalar = _, source = $query, args = [$($args)*], checked = false)
435    );
436}
437
438/// Compile-time checked SQL query read from a file at compile time.
439///
440/// The file path is relative to the crate root. This keeps large queries out
441/// of your Rust source files.
442///
443/// ```ignore
444/// # use sqlx_mssql_odbc::MssqlConnection;
445/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
446/// # let mut conn = MssqlConnection::connect("…").await?;
447/// let reports = sqlx_mssql_odbc::query_file!("queries/monthly_report.sql")
448///     .fetch_all(&mut conn)
449///     .await?;
450/// # Ok(())
451/// # }
452/// ```
453///
454/// Requires the `macros` feature.
455#[cfg(feature = "macros")]
456#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
457#[macro_export]
458macro_rules! query_file {
459    ($path:literal) => ({
460        $crate::expand_query!(source_file = $path)
461    });
462    ($path:literal, $($args:tt)*) => ({
463        $crate::expand_query!(source_file = $path, args = [$($args)*])
464    });
465}
466
467/// Compile-time SQL query read from a file (unchecked variant).
468///
469/// Like [`query_file!`] but does **not** verify the query against a live
470/// database or offline cache.
471///
472/// Requires the `macros` feature.
473#[cfg(feature = "macros")]
474#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
475#[macro_export]
476macro_rules! query_file_unchecked {
477    ($path:literal) => ({
478        $crate::expand_query!(source_file = $path, checked = false)
479    });
480    ($path:literal, $($args:tt)*) => ({
481        $crate::expand_query!(source_file = $path, args = [$($args)*], checked = false)
482    });
483}
484
485/// Compile-time checked SQL query read from a file, mapping to a named struct.
486///
487/// Combines [`query_file!`] and [`query_as!`]: the SQL lives in a separate
488/// `.sql` file and rows are deserialised into the given struct.
489///
490/// ```ignore
491/// # use sqlx_mssql_odbc::MssqlConnection;
492/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
493/// # let mut conn = MssqlConnection::connect("…").await?;
494/// struct UserReport {
495///     name: String,
496///     order_count: i64,
497/// }
498///
499/// let reports = sqlx_mssql_odbc::query_file_as!(UserReport, "queries/user_reports.sql")
500///     .fetch_all(&mut conn)
501///     .await?;
502/// # Ok(())
503/// # }
504/// ```
505///
506/// Requires the `macros` feature.
507#[cfg(feature = "macros")]
508#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
509#[macro_export]
510macro_rules! query_file_as {
511    ($out_struct:path, $path:literal) => ({
512        $crate::expand_query!(record = $out_struct, source_file = $path)
513    });
514    ($out_struct:path, $path:literal, $($args:tt)*) => ({
515        $crate::expand_query!(record = $out_struct, source_file = $path, args = [$($args)*])
516    });
517}
518
519/// Compile-time SQL query read from a file, mapping to a struct (unchecked
520/// variant).
521///
522/// Like [`query_file_as!`] but skips database verification at compile time.
523///
524/// Requires the `macros` feature.
525#[cfg(feature = "macros")]
526#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
527#[macro_export]
528macro_rules! query_file_as_unchecked {
529    ($out_struct:path, $path:literal) => ({
530        $crate::expand_query!(record = $out_struct, source_file = $path, checked = false)
531    });
532    ($out_struct:path, $path:literal, $($args:tt)*) => ({
533        $crate::expand_query!(record = $out_struct, source_file = $path, args = [$($args)*], checked = false)
534    });
535}
536
537/// Compile-time checked SQL query read from a file, returning a single scalar.
538///
539/// Like [`query_file!`] but returns a single value — useful for report
540/// totals, row counts, or other aggregate queries stored in files.
541///
542/// ```ignore
543/// # use sqlx_mssql_odbc::MssqlConnection;
544/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
545/// # let mut conn = MssqlConnection::connect("…").await?;
546/// let total: f64 = sqlx_mssql_odbc::query_file_scalar!("queries/total_revenue.sql")
547///     .fetch_one(&mut conn)
548///     .await?;
549/// # Ok(())
550/// # }
551/// ```
552///
553/// Requires the `macros` feature.
554#[cfg(feature = "macros")]
555#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
556#[macro_export]
557macro_rules! query_file_scalar {
558    ($path:literal) => (
559        $crate::expand_query!(scalar = _, source_file = $path)
560    );
561    ($path:literal, $($args:tt)*) => (
562        $crate::expand_query!(scalar = _, source_file = $path, args = [$($args)*])
563    );
564}
565
566/// Compile-time SQL query read from a file, returning a single scalar
567/// (unchecked variant).
568///
569/// Like [`query_file_scalar!`] but skips database verification at compile
570/// time.
571///
572/// Requires the `macros` feature.
573#[cfg(feature = "macros")]
574#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
575#[macro_export]
576macro_rules! query_file_scalar_unchecked {
577    ($path:literal) => (
578        $crate::expand_query!(scalar = _, source_file = $path, checked = false)
579    );
580    ($path:literal, $($args:tt)*) => (
581        $crate::expand_query!(scalar = _, source_file = $path, args = [$($args)*], checked = false)
582    );
583}
584
585// Re-export derive macros behind `derive` feature.
586#[cfg(feature = "derive")]
587#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
588pub use sqlx_mssql_odbc_macros::{Encode, Decode, Type, FromRow};