Skip to main content

tiberius/
lib.rs

1//! An asynchronous, runtime-independent, pure-rust Tabular Data Stream (TDS)
2//! implementation for Microsoft SQL Server.
3//!
4//! Tiberius is not bound to any single async runtime: a `TcpStream` is created
5//! separately and injected into the [`Client`], so it works with Tokio, smol,
6//! and other runtimes that provide `futures::io::{AsyncRead, AsyncWrite}`.
7//!
8//! # Connecting with Tokio
9//!
10//! Tokio is using their own version of `AsyncRead` and `AsyncWrite` traits,
11//! meaning that when wanting to use Tiberius with Tokio, their `TcpStream`
12//! needs to be wrapped in Tokio's `Compat` module.
13//!
14//! ```no_run
15//! use tiberius::{Client, Config, AuthMethod};
16//! use tokio::net::TcpStream;
17//! use tokio_util::compat::TokioAsyncWriteCompatExt;
18//!
19//! #[tokio::main]
20//! async fn main() -> anyhow::Result<()> {
21//!     let mut config = Config::new();
22//!
23//!     config.host("localhost");
24//!     config.port(1433);
25//!     config.authentication(AuthMethod::sql_server("SA", "<YourStrong@Passw0rd>"));
26//!     config.trust_cert(); // on production, it is not a good idea to do this
27//!
28//!     let tcp = TcpStream::connect(config.get_addr()).await?;
29//!     tcp.set_nodelay(true)?;
30//!
31//!     // To be able to use Tokio's tcp, we're using the `compat_write` from
32//!     // the `TokioAsyncWriteCompatExt` to get a stream compatible with the
33//!     // traits from the `futures` crate.
34//!     let mut client = Client::connect(config, tcp.compat_write()).await?;
35//!     # client.query("SELECT @P1", &[&-4i32]).await?;
36//!
37//!     Ok(())
38//! }
39//! ```
40//!
41//! # Ways of querying
42//!
43//! Tiberius offers two ways to query the database: directly from the [`Client`]
44//! with the [`Client#query`] and [`Client#execute`], or additionally through
45//! the [`Query`] object.
46//!
47//! ### With the client methods
48//!
49//! When the query parameters are known when writing the code, the client methods
50//! are easy to use.
51//!
52//! ```no_run
53//! # use tiberius::{Client, Config, AuthMethod};
54//! # use tokio::net::TcpStream;
55//! # use tokio_util::compat::TokioAsyncWriteCompatExt;
56//! # #[tokio::main]
57//! # async fn main() -> anyhow::Result<()> {
58//! # let mut config = Config::new();
59//! # config.host("localhost");
60//! # config.port(1433);
61//! # config.authentication(AuthMethod::sql_server("SA", "<YourStrong@Passw0rd>"));
62//! # config.trust_cert();
63//! # let tcp = TcpStream::connect(config.get_addr()).await?;
64//! # tcp.set_nodelay(true)?;
65//! # let mut client = Client::connect(config, tcp.compat_write()).await?;
66//! let _res = client.query("SELECT @P1", &[&-4i32]).await?;
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! ### With the Query object
72//!
73//! In case of needing to pass the parameters from a dynamic collection, or if
74//! wanting to pass them by-value, use the [`Query`] object.
75//!
76//! ```no_run
77//! # use tiberius::{Client, Query, Config, AuthMethod};
78//! # use tokio::net::TcpStream;
79//! # use tokio_util::compat::TokioAsyncWriteCompatExt;
80//! # #[tokio::main]
81//! # async fn main() -> anyhow::Result<()> {
82//! # let mut config = Config::new();
83//! # config.host("localhost");
84//! # config.port(1433);
85//! # config.authentication(AuthMethod::sql_server("SA", "<YourStrong@Passw0rd>"));
86//! # config.trust_cert();
87//! # let tcp = TcpStream::connect(config.get_addr()).await?;
88//! # tcp.set_nodelay(true)?;
89//! # let mut client = Client::connect(config, tcp.compat_write()).await?;
90//! let params = vec![String::from("foo"), String::from("bar")];
91//! let mut select = Query::new("SELECT @P1, @P2, @P3");
92//!
93//! for param in params.into_iter() {
94//!     select.bind(param);
95//! }
96//!
97//! let _res = select.query(&mut client).await?;
98//! # Ok(())
99//! # }
100//! ```
101//!
102//! # Authentication
103//!
104//! Tiberius supports different [ways of authentication] to the SQL Server:
105//!
106//! - SQL Server authentication uses the facilities of the database to
107//!   authenticate the user.
108//! - On Windows, you can authenticate using the currently logged in user or
109//!   specified Windows credentials.
110//! - If enabling the `integrated-auth-gssapi` feature, it is possible to login
111//!   with the currently active Kerberos credentials.
112//!
113//! ## AAD(Azure Active Directory) Authentication
114//!
115//! Tiberius supports AAD authentication by taking an AAD token. Suggest using
116//! [azure_identity](https://crates.io/crates/azure_identity) crate to retrieve
117//! the token, and config tiberius with token. There is an example in examples
118//! folder on how to setup this.
119//!
120//! # TLS
121//!
122//! When compiled using the default features, a TLS encryption will be available
123//! and by default, used for all traffic. TLS is handled with the given
124//! `TcpStream`. Please see the documentation for [`EncryptionLevel`] for
125//! details.
126//!
127//! # SQL Browser
128//!
129//! On Windows platforms, connecting to the SQL Server might require going through
130//! the SQL Browser service to get the correct port for the named instance. This
131//! feature requires the `sql-browser-tokio` (or `sql-browser-smol`) feature flag
132//! to be enabled and has a bit different way of connecting:
133//!
134//! ```no_run
135//! # #[cfg(feature = "sql-browser-tokio")]
136//! use tiberius::{Client, Config, AuthMethod};
137//! # #[cfg(feature = "sql-browser-tokio")]
138//! use tokio::net::TcpStream;
139//! # #[cfg(feature = "sql-browser-tokio")]
140//! use tokio_util::compat::TokioAsyncWriteCompatExt;
141//!
142//! // An extra trait that allows connecting to a named instance with the given
143//! // `TcpStream`.
144//! # #[cfg(feature = "sql-browser-tokio")]
145//! use tiberius::SqlBrowser;
146//!
147//! # #[cfg(feature = "sql-browser-tokio")]
148//! #[tokio::main]
149//! async fn main() -> anyhow::Result<()> {
150//!     let mut config = Config::new();
151//!
152//!     config.authentication(AuthMethod::sql_server("SA", "<password>"));
153//!     config.host("localhost");
154//!
155//!     // The default port of SQL Browser
156//!     config.port(1434);
157//!
158//!     // The name of the database server instance.
159//!     config.instance_name("INSTANCE");
160//!
161//!     // on production, it is not a good idea to do this
162//!     config.trust_cert();
163//!
164//!     // This will create a new `TcpStream`, connected to the right port of the
165//!     // named instance.
166//!     let tcp = TcpStream::connect_named(&config).await?;
167//!
168//!     // And from here on continue the connection process in a normal way.
169//!     let mut client = Client::connect(config, tcp.compat_write()).await?;
170//!     # client.query("SELECT @P1", &[&-4i32]).await?;
171//!     Ok(())
172//! }
173//! # #[cfg(not(feature = "sql-browser-tokio"))]
174//! # fn main() {}
175//! ```
176//!
177//! # Other features
178//!
179//! - If using an [ADO.NET connection string], it is possible to create a
180//!   [`Config`] from one. Please see the documentation for
181//!   [`from_ado_string`] for details.
182//! - If wanting to use Tiberius with SQL Server version 2005, one must
183//!   disable the `tds73` feature.
184//!
185//! [`EncryptionLevel`]: enum.EncryptionLevel.html
186//! [`Client`]: struct.Client.html
187//! [`Client#query`]: struct.Client.html#method.query
188//! [`Client#execute`]: struct.Client.html#method.execute
189//! [`Query`]: struct.Query.html
190//! [`Query#bind`]: struct.Query.html#method.bind
191//! [`Config`]: struct.Config.html
192//! [`from_ado_string`]: struct.Config.html#method.from_ado_string
193//! [`time`]: time/index.html
194//! [ways of authentication]: enum.AuthMethod.html
195//! [ADO.NET connection string]: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings
196#![cfg_attr(docsrs, feature(doc_cfg))]
197#![recursion_limit = "512"]
198#![warn(missing_docs)]
199#![warn(missing_debug_implementations, rust_2018_idioms)]
200#![doc(test(attr(deny(rust_2018_idioms, warnings))))]
201#![doc(test(attr(allow(unused_extern_crates, unused_variables))))]
202
203#[cfg(all(
204    feature = "tds80",
205    not(any(
206        feature = "rustls",
207        feature = "native-tls",
208        feature = "vendored-openssl"
209    ))
210))]
211compile_error!("The `tds80` feature requires one of the TLS features to be enabled.");
212
213#[cfg(feature = "bigdecimal")]
214pub(crate) extern crate bigdecimal_ as bigdecimal;
215
216#[macro_use]
217mod macros;
218
219mod client;
220mod command;
221mod from_sql;
222mod query;
223mod sql_read_bytes;
224mod to_sql;
225
226pub mod error;
227mod result;
228mod row;
229mod tds;
230
231mod sql_browser;
232
233pub use client::{AuthMethod, Client, Config, ConfigBuilder};
234pub use command::{Command, SqlTableData, SqlTableDataRow, TableValue, TableValueRow};
235pub(crate) use error::Error;
236pub use from_sql::{FromSql, FromSqlOwned};
237pub use query::Query;
238pub use result::*;
239pub use row::{Column, ColumnType, QueryIdx, Row};
240pub use sql_browser::SqlBrowser;
241pub use tds::{
242    codec::{
243        AltMetaDataColumn, BaseMetaDataColumn, BulkLoadRequest, ColumnData, ColumnFlag,
244        FixedLenType, IntoRow, IsolationLevel, MetaDataColumn, TokenAltMetaData, TokenAltRow,
245        TokenRow, TypeInfo, TypeLength, VarLenContext, VarLenType,
246    },
247    collation::Collation,
248    numeric,
249    stream::{CommandReturnValue, CommandStream, QueryStream},
250    time, xml, EncryptionLevel,
251};
252pub use to_sql::{IntoSql, ToSql};
253pub use uuid::Uuid;
254
255use sql_read_bytes::*;
256use tds::codec::*;
257
258/// An alias for a result that holds crate's error type as the error.
259pub type Result<T> = std::result::Result<T, Error>;
260
261pub(crate) fn get_driver_version() -> u64 {
262    encode_driver_version(env!("CARGO_PKG_VERSION"))
263}
264
265/// Packs a dotted version string into the little-endian byte layout the TDS
266/// login record expects: the first component in the low byte, the next in bits
267/// 8..16, and so on (up to six components). Non-numeric components contribute
268/// zero.
269fn encode_driver_version(version: &str) -> u64 {
270    version
271        .splitn(6, '.')
272        .enumerate()
273        .fold(0u64, |acc, part| match part.1.parse::<u64>() {
274            Ok(num) => acc | num << (part.0 * 8),
275            // A non-numeric component contributes nothing.
276            _ => acc,
277        })
278}
279
280#[cfg(test)]
281mod driver_version_tests {
282    use super::encode_driver_version;
283
284    #[test]
285    fn packs_each_component_into_its_own_byte() {
286        // 1 | 2<<8 | 3<<16 = 0x030201
287        assert_eq!(encode_driver_version("1.2.3"), 0x03_02_01);
288        // Distinct values per position pin the shift amounts.
289        assert_eq!(encode_driver_version("4.5.6.7"), 0x07_06_05_04);
290    }
291
292    #[test]
293    fn shift_moves_components_left_not_right() {
294        // With `>>` instead of `<<`, `17 >> 8 == 0`, so the minor version would
295        // vanish and the result would collapse to just the major (34).
296        assert_eq!(encode_driver_version("34.17"), 34 | (17 << 8));
297        assert_ne!(encode_driver_version("34.17"), 34);
298    }
299
300    #[test]
301    fn components_are_combined_with_or_not_xor() {
302        // 257 (0x101) in byte 0 shares bit 8 with `1 << 8` (0x100). OR keeps the
303        // bit set (0x101); XOR would clear it (0x001), so this pins `|` vs `^`.
304        assert_eq!(encode_driver_version("257.1"), 0x101);
305    }
306
307    #[test]
308    fn non_numeric_components_contribute_zero() {
309        assert_eq!(encode_driver_version("1.beta.3"), 1 | (3 << 16));
310        assert_eq!(encode_driver_version("notaversion"), 0);
311    }
312
313    #[test]
314    fn get_driver_version_encodes_the_crate_version() {
315        // Pins the wrapper to the real version so a body-replacement mutant
316        // (e.g. "-> 0" or "-> 1") is caught.
317        assert_eq!(
318            super::get_driver_version(),
319            encode_driver_version(env!("CARGO_PKG_VERSION"))
320        );
321        assert_ne!(super::get_driver_version(), 0);
322    }
323}