Skip to main content

mssql_client/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![deny(unsafe_code)]
4
5// Module dependency graph (acyclic):
6//
7//   client ──→ config, state, error, stream, transaction, statement_cache
8//     ├── connect.rs ──→ config, state, instrumentation, mssql_tls, mssql_codec, tds_protocol
9//     ├── params.rs  ──→ mssql_types, tds_protocol
10//     └── response.rs ──→ error, mssql_codec, tds_protocol
11//   procedure ──→ client, error, state, stream, tds_protocol
12//   stream ──→ error, row
13//   row ──→ blob, error, mssql_types
14//   config ──→ mssql_auth, mssql_tls, tds_protocol
15//   bulk ──→ error, mssql_types, tds_protocol
16//   cancel ──→ error, mssql_codec, mssql_tls
17//   encryption ──→ mssql_auth, tds_protocol
18//   column_parser ──→ error, mssql_types, tds_protocol
19
20pub mod blob;
21pub mod blob_stream;
22pub(crate) mod browser;
23pub mod bulk;
24pub mod cancel;
25pub mod change_tracking;
26pub mod client;
27#[cfg(feature = "always-encrypted")]
28pub(crate) mod column_decryptor;
29pub(crate) mod column_parser;
30pub mod config;
31pub mod encryption;
32pub mod error;
33#[cfg(all(windows, feature = "filestream"))]
34#[allow(unsafe_code)] // Win32 FFI for OpenSqlFilestream; see SAFETY comments in each unsafe block
35pub mod filestream;
36pub mod from_row;
37pub mod instrumentation;
38pub(crate) mod plp;
39pub mod procedure;
40pub mod query;
41pub mod row;
42// Sans-IO incremental token decoder driving the streaming read path.
43pub(crate) mod row_source;
44pub mod row_stream;
45pub mod state;
46// Not yet wired into the query path (queries use sp_executesql); kept
47// crate-private until the cache is actually used, so the public API does not
48// expose types for an unshipped feature. Re-export when it lands.
49pub(crate) mod statement_cache;
50pub mod stream;
51pub mod to_params;
52pub mod transaction;
53pub mod tvp;
54pub(crate) mod validation;
55
56// Re-export commonly used types
57pub use bulk::{
58    BulkColumn, BulkInsert, BulkInsertBuilder, BulkInsertResult, BulkOptions, BulkWriter,
59};
60pub use cancel::CancelHandle;
61pub use client::Client;
62pub use config::{ApplicationIntent, Config, RedirectConfig, RetryPolicy, TimeoutConfig};
63pub use error::{Error, SharedIoError};
64// Sub-error types carried by `Error` variants and the `FromSql`/`ToSql` trait
65// return type. Re-exported so downstream crates can name them (e.g. match on
66// `Error::Type(e)`, or write `fn from_sql(..) -> Result<Self, TypeError>`)
67// without depending on the internal crates directly. `EncryptionError` is
68// intentionally NOT here: `Error` stringifies it (see `error.rs`) so key
69// material cannot leak, and it appears in no other public signature.
70pub use mssql_auth::AuthError;
71pub use mssql_codec::CodecError;
72#[cfg(feature = "tls")]
73pub use mssql_tls::TlsError;
74pub use mssql_types::TypeError;
75pub use tds_protocol::ProtocolError;
76
77// TLS configuration: re-export so the `Config::tls` field is usable (custom
78// root certificates, client auth) without a direct `mssql-tls` dependency.
79// `CertificateDer` is needed to add a root certificate.
80#[cfg(feature = "tls")]
81pub use mssql_tls::{CertificateDer, TlsConfig};
82
83// `KeyStoreProvider` extension trait: users implement it for custom Always
84// Encrypted key stores (per the encryption-module docs) without a direct
85// `mssql-auth` dependency.
86#[cfg(feature = "always-encrypted")]
87pub use mssql_auth::KeyStoreProvider;
88
89// `Collation` appears on `Column::collation` and the `with_collation` builders
90// (Column, BulkColumn); re-export so those are usable without a direct
91// `tds-protocol` dependency.
92pub use tds_protocol::token::Collation;
93
94// Derive macros, re-exported under the `derive` feature so users need only a
95// single `mssql-client` dependency (the macros' generated code resolves all
96// its paths through `mssql_client`, including `__private` below). The macro
97// names intentionally match the trait names — they live in the macro
98// namespace, so `#[derive(FromRow)]` and `impl FromRow` coexist (as with
99// serde's `Serialize`).
100#[cfg(feature = "derive")]
101pub use mssql_derive::{FromRow, ToParams, Tvp};
102
103/// Items the derive macros' generated code references. Not public API: hidden
104/// from docs and exempt from stability guarantees. Centralizing them here
105/// keeps the proc-macro crate decoupled from internal restructuring.
106#[doc(hidden)]
107pub mod __private {
108    pub use mssql_types::{ToSql, TypeError};
109}
110
111// Re-export TDS version for configuration
112pub use from_row::{FromRow, MapRows, RowIteratorExt};
113pub use mssql_auth::Credentials;
114pub use tds_protocol::version::TdsVersion;
115
116// Secure credential types (with zeroize feature)
117#[cfg(feature = "zeroize")]
118pub use mssql_auth::{SecretString, SecureCredentials};
119pub use mssql_types::{
120    Binary, Char, EncryptedParamType, FromSql, NChar, SqlTyped, SqlValue, ToSql, TypedNull, binary,
121    char, nchar, null,
122};
123#[cfg(feature = "chrono")]
124pub use mssql_types::{
125    DateTime2, DateTimeLegacy, DateTimeOffset, SmallDateTime, Time, datetime, datetime2,
126    datetimeoffset, time,
127};
128#[cfg(feature = "decimal")]
129pub use mssql_types::{Money, Numeric, SmallMoney, numeric};
130pub use procedure::ProcedureBuilder;
131pub use query::in_params;
132pub use row::{Column, Row};
133pub use state::{Connected, ConnectionState, Disconnected, InTransaction, ProtocolState, Ready};
134
135/// Internal entry points for the fuzzing harness in `fuzz/`.
136///
137/// Enabled only by the `fuzzing` feature; not public API and exempt from
138/// all stability guarantees.
139#[cfg(feature = "fuzzing")]
140#[doc(hidden)]
141pub mod __fuzzing {
142    pub use crate::column_parser::parse_column_value;
143}
144
145/// Internal entry point for the allocation benchmark in `benches/`.
146///
147/// Enabled only by the `bench` feature; not public API and exempt from all
148/// stability guarantees.
149#[cfg(feature = "bench")]
150#[doc(hidden)]
151#[allow(clippy::expect_used)]
152pub mod __bench {
153    use bytes::Bytes;
154    use tds_protocol::token::{Token, TokenParser};
155
156    use crate::Ready;
157    use crate::client::Client;
158    use crate::row::Row;
159
160    /// Decode a complete buffered query response (one ColMetaData followed by
161    /// ROW tokens) into materialized `Row`s, with no async or socket IO.
162    ///
163    /// Mirrors the buffered read path over the same functions production uses:
164    /// Stage A drives `TokenParser` (as in `read_query_response`) and Stage B
165    /// runs `build_columns` + `convert_raw_row` (as in `QueryStream`). It
166    /// exists only to give the allocation benchmark a deterministic, in-memory
167    /// seam.
168    #[must_use]
169    pub fn decode_buffered_response(bytes: Bytes) -> Vec<Row> {
170        let mut parser = TokenParser::new(bytes);
171        let mut row_meta = std::sync::Arc::new(crate::row::ColMetaData::new(Vec::new()));
172        let mut meta = None;
173        let mut rows = Vec::new();
174        while let Some(token) = parser
175            .next_token_with_metadata(meta.as_ref())
176            .expect("benchmark fixture must decode")
177        {
178            match token {
179                Token::ColMetaData(m) => {
180                    row_meta = std::sync::Arc::new(crate::row::ColMetaData::new(
181                        Client::<Ready>::build_columns(&m),
182                    ));
183                    meta = Some(m);
184                }
185                Token::Row(raw) => {
186                    let m = meta.as_ref().expect("ColMetaData precedes ROW tokens");
187                    rows.push(
188                        crate::column_parser::convert_raw_row(&raw, m, &row_meta)
189                            .expect("benchmark row must convert"),
190                    );
191                }
192                _ => {}
193            }
194        }
195        rows
196    }
197}
198pub use blob_stream::BlobStream;
199pub use row_stream::RowStream;
200pub use stream::{
201    ExecuteResult, MultiResultStream, OutputParam, ProcedureResult, QueryStream, ResultSet,
202};
203pub use to_params::{NamedParam, ParamList, ToParams};
204pub use transaction::{IsolationLevel, SavePoint, Transaction};
205pub use tvp::{Tvp, TvpColumn, TvpRow, TvpValue};
206
207// FILESTREAM support (Windows only)
208#[cfg(all(windows, feature = "filestream"))]
209pub use filestream::{FileStream, FileStreamAccess, open_options as filestream_options};
210
211// Always Encrypted types
212pub use encryption::EncryptionConfig;
213
214// OpenTelemetry instrumentation (available whether or not otel feature is enabled)
215pub use instrumentation::{
216    DatabaseMetrics, OperationTimer, SanitizationConfig, attributes, metric_names, span_names,
217};
218
219// Change Tracking support
220pub use change_tracking::{
221    ChangeMetadata, ChangeOperation, ChangeTracking, ChangeTrackingQuery, SyncVersionStatus,
222};
223
224#[cfg(test)]
225mod auto_trait_tests {
226    //! Compile-time assertions that key async types are Send + Sync.
227    //!
228    //! These tests catch regressions where a type accidentally becomes
229    //! !Send or !Sync due to interior changes (e.g., adding an Rc, Cell,
230    //! or non-Send future). They cost nothing at runtime.
231
232    use super::*;
233
234    fn assert_send<T: Send>() {}
235    fn assert_sync<T: Sync>() {}
236
237    // --- Type-state Client variants ---
238    #[test]
239    fn client_ready_is_send_sync() {
240        assert_send::<Client<Ready>>();
241        assert_sync::<Client<Ready>>();
242    }
243
244    #[test]
245    fn client_in_transaction_is_send_sync() {
246        assert_send::<Client<InTransaction>>();
247        assert_sync::<Client<InTransaction>>();
248    }
249
250    #[test]
251    fn client_disconnected_is_send_sync() {
252        assert_send::<Client<Disconnected>>();
253        assert_sync::<Client<Disconnected>>();
254    }
255
256    #[test]
257    fn client_connected_is_send_sync() {
258        assert_send::<Client<Connected>>();
259        assert_sync::<Client<Connected>>();
260    }
261
262    // --- Configuration ---
263    #[test]
264    fn config_is_send_sync() {
265        assert_send::<Config>();
266        assert_sync::<Config>();
267    }
268
269    // --- Streaming types ---
270    #[test]
271    fn query_stream_is_send_sync() {
272        assert_send::<QueryStream<'_>>();
273        assert_sync::<QueryStream<'_>>();
274    }
275
276    #[test]
277    fn row_stream_is_send_sync() {
278        assert_send::<RowStream<'_>>();
279        assert_sync::<RowStream<'_>>();
280    }
281
282    #[test]
283    fn blob_stream_is_send_sync() {
284        assert_send::<BlobStream<'_>>();
285        assert_sync::<BlobStream<'_>>();
286    }
287
288    #[test]
289    fn multi_result_stream_is_send_sync() {
290        assert_send::<MultiResultStream<'_>>();
291        assert_sync::<MultiResultStream<'_>>();
292    }
293
294    #[test]
295    fn result_set_is_send_sync() {
296        assert_send::<ResultSet>();
297        assert_sync::<ResultSet>();
298    }
299
300    #[test]
301    fn execute_result_is_send_sync() {
302        assert_send::<ExecuteResult>();
303        assert_sync::<ExecuteResult>();
304    }
305
306    #[test]
307    fn procedure_result_is_send_sync() {
308        assert_send::<ProcedureResult>();
309        assert_sync::<ProcedureResult>();
310    }
311
312    #[test]
313    fn procedure_builder_is_send_sync() {
314        assert_send::<ProcedureBuilder<'_, Ready>>();
315        assert_sync::<ProcedureBuilder<'_, Ready>>();
316    }
317
318    // --- Bulk insert types ---
319    #[test]
320    fn bulk_insert_is_send_sync() {
321        assert_send::<BulkInsert>();
322        assert_sync::<BulkInsert>();
323    }
324
325    #[test]
326    fn bulk_insert_builder_is_send_sync() {
327        assert_send::<BulkInsertBuilder>();
328        assert_sync::<BulkInsertBuilder>();
329    }
330
331    #[test]
332    fn bulk_options_is_send_sync() {
333        assert_send::<BulkOptions>();
334        assert_sync::<BulkOptions>();
335    }
336
337    // --- Cancel handle ---
338    #[test]
339    fn cancel_handle_is_send_sync() {
340        assert_send::<CancelHandle>();
341        assert_sync::<CancelHandle>();
342    }
343
344    // --- Row and column types ---
345    #[test]
346    fn row_is_send_sync() {
347        assert_send::<Row>();
348        assert_sync::<Row>();
349    }
350
351    #[test]
352    fn column_is_send_sync() {
353        assert_send::<Column>();
354        assert_sync::<Column>();
355    }
356
357    // --- Statement cache (crate-private until wired) ---
358    #[test]
359    fn statement_cache_is_send_sync() {
360        use crate::statement_cache::StatementCache;
361        assert_send::<StatementCache>();
362        assert_sync::<StatementCache>();
363    }
364
365    // --- Error type ---
366    #[test]
367    fn error_is_send_sync() {
368        assert_send::<Error>();
369        assert_sync::<Error>();
370    }
371}