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