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}