tiberius-ng 0.13.1

A TDS (Microsoft SQL Server) driver for Rust — actively-maintained community continuation of tiberius
Documentation
//! An asynchronous, runtime-independent, pure-rust Tabular Data Stream (TDS)
//! implementation for Microsoft SQL Server.
//!
//! Tiberius is not bound to any single async runtime: a `TcpStream` is created
//! separately and injected into the [`Client`], so it works with Tokio, smol,
//! and other runtimes that provide `futures::io::{AsyncRead, AsyncWrite}`.
//!
//! # Connecting with Tokio
//!
//! Tokio is using their own version of `AsyncRead` and `AsyncWrite` traits,
//! meaning that when wanting to use Tiberius with Tokio, their `TcpStream`
//! needs to be wrapped in Tokio's `Compat` module.
//!
//! ```no_run
//! use tiberius::{Client, Config, AuthMethod};
//! use tokio::net::TcpStream;
//! use tokio_util::compat::TokioAsyncWriteCompatExt;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let mut config = Config::new();
//!
//!     config.host("localhost");
//!     config.port(1433);
//!     config.authentication(AuthMethod::sql_server("SA", "<YourStrong@Passw0rd>"));
//!     config.trust_cert(); // on production, it is not a good idea to do this
//!
//!     let tcp = TcpStream::connect(config.get_addr()).await?;
//!     tcp.set_nodelay(true)?;
//!
//!     // To be able to use Tokio's tcp, we're using the `compat_write` from
//!     // the `TokioAsyncWriteCompatExt` to get a stream compatible with the
//!     // traits from the `futures` crate.
//!     let mut client = Client::connect(config, tcp.compat_write()).await?;
//!     # client.query("SELECT @P1", &[&-4i32]).await?;
//!
//!     Ok(())
//! }
//! ```
//!
//! # Ways of querying
//!
//! Tiberius offers two ways to query the database: directly from the [`Client`]
//! with the [`Client#query`] and [`Client#execute`], or additionally through
//! the [`Query`] object.
//!
//! ### With the client methods
//!
//! When the query parameters are known when writing the code, the client methods
//! are easy to use.
//!
//! ```no_run
//! # use tiberius::{Client, Config, AuthMethod};
//! # use tokio::net::TcpStream;
//! # use tokio_util::compat::TokioAsyncWriteCompatExt;
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # let mut config = Config::new();
//! # config.host("localhost");
//! # config.port(1433);
//! # config.authentication(AuthMethod::sql_server("SA", "<YourStrong@Passw0rd>"));
//! # config.trust_cert();
//! # let tcp = TcpStream::connect(config.get_addr()).await?;
//! # tcp.set_nodelay(true)?;
//! # let mut client = Client::connect(config, tcp.compat_write()).await?;
//! let _res = client.query("SELECT @P1", &[&-4i32]).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### With the Query object
//!
//! In case of needing to pass the parameters from a dynamic collection, or if
//! wanting to pass them by-value, use the [`Query`] object.
//!
//! ```no_run
//! # use tiberius::{Client, Query, Config, AuthMethod};
//! # use tokio::net::TcpStream;
//! # use tokio_util::compat::TokioAsyncWriteCompatExt;
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # let mut config = Config::new();
//! # config.host("localhost");
//! # config.port(1433);
//! # config.authentication(AuthMethod::sql_server("SA", "<YourStrong@Passw0rd>"));
//! # config.trust_cert();
//! # let tcp = TcpStream::connect(config.get_addr()).await?;
//! # tcp.set_nodelay(true)?;
//! # let mut client = Client::connect(config, tcp.compat_write()).await?;
//! let params = vec![String::from("foo"), String::from("bar")];
//! let mut select = Query::new("SELECT @P1, @P2, @P3");
//!
//! for param in params.into_iter() {
//!     select.bind(param);
//! }
//!
//! let _res = select.query(&mut client).await?;
//! # Ok(())
//! # }
//! ```
//!
//! # Authentication
//!
//! Tiberius supports different [ways of authentication] to the SQL Server:
//!
//! - SQL Server authentication uses the facilities of the database to
//!   authenticate the user.
//! - On Windows, you can authenticate using the currently logged in user or
//!   specified Windows credentials.
//! - If enabling the `integrated-auth-gssapi` feature, it is possible to login
//!   with the currently active Kerberos credentials.
//!
//! ## AAD(Azure Active Directory) Authentication
//!
//! Tiberius supports AAD authentication by taking an AAD token. Suggest using
//! [azure_identity](https://crates.io/crates/azure_identity) crate to retrieve
//! the token, and config tiberius with token. There is an example in examples
//! folder on how to setup this.
//!
//! # TLS
//!
//! When compiled using the default features, a TLS encryption will be available
//! and by default, used for all traffic. TLS is handled with the given
//! `TcpStream`. Please see the documentation for [`EncryptionLevel`] for
//! details.
//!
//! # SQL Browser
//!
//! On Windows platforms, connecting to the SQL Server might require going through
//! the SQL Browser service to get the correct port for the named instance. This
//! feature requires the `sql-browser-tokio` (or `sql-browser-smol`) feature flag
//! to be enabled and has a bit different way of connecting:
//!
//! ```no_run
//! # #[cfg(feature = "sql-browser-tokio")]
//! use tiberius::{Client, Config, AuthMethod};
//! # #[cfg(feature = "sql-browser-tokio")]
//! use tokio::net::TcpStream;
//! # #[cfg(feature = "sql-browser-tokio")]
//! use tokio_util::compat::TokioAsyncWriteCompatExt;
//!
//! // An extra trait that allows connecting to a named instance with the given
//! // `TcpStream`.
//! # #[cfg(feature = "sql-browser-tokio")]
//! use tiberius::SqlBrowser;
//!
//! # #[cfg(feature = "sql-browser-tokio")]
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let mut config = Config::new();
//!
//!     config.authentication(AuthMethod::sql_server("SA", "<password>"));
//!     config.host("localhost");
//!
//!     // The default port of SQL Browser
//!     config.port(1434);
//!
//!     // The name of the database server instance.
//!     config.instance_name("INSTANCE");
//!
//!     // on production, it is not a good idea to do this
//!     config.trust_cert();
//!
//!     // This will create a new `TcpStream`, connected to the right port of the
//!     // named instance.
//!     let tcp = TcpStream::connect_named(&config).await?;
//!
//!     // And from here on continue the connection process in a normal way.
//!     let mut client = Client::connect(config, tcp.compat_write()).await?;
//!     # client.query("SELECT @P1", &[&-4i32]).await?;
//!     Ok(())
//! }
//! # #[cfg(not(feature = "sql-browser-tokio"))]
//! # fn main() {}
//! ```
//!
//! # Other features
//!
//! - If using an [ADO.NET connection string], it is possible to create a
//!   [`Config`] from one. Please see the documentation for
//!   [`from_ado_string`] for details.
//! - If wanting to use Tiberius with SQL Server version 2005, one must
//!   disable the `tds73` feature.
//!
//! [`EncryptionLevel`]: enum.EncryptionLevel.html
//! [`Client`]: struct.Client.html
//! [`Client#query`]: struct.Client.html#method.query
//! [`Client#execute`]: struct.Client.html#method.execute
//! [`Query`]: struct.Query.html
//! [`Query#bind`]: struct.Query.html#method.bind
//! [`Config`]: struct.Config.html
//! [`from_ado_string`]: struct.Config.html#method.from_ado_string
//! [`time`]: time/index.html
//! [ways of authentication]: enum.AuthMethod.html
//! [ADO.NET connection string]: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings
#![cfg_attr(docsrs, feature(doc_cfg))]
#![recursion_limit = "512"]
#![warn(missing_docs)]
#![warn(missing_debug_implementations, rust_2018_idioms)]
#![doc(test(attr(deny(rust_2018_idioms, warnings))))]
#![doc(test(attr(allow(unused_extern_crates, unused_variables))))]

#[cfg(all(
    feature = "tds80",
    not(any(
        feature = "rustls",
        feature = "native-tls",
        feature = "vendored-openssl"
    ))
))]
compile_error!("The `tds80` feature requires one of the TLS features to be enabled.");

#[cfg(feature = "bigdecimal")]
pub(crate) extern crate bigdecimal_ as bigdecimal;

#[macro_use]
mod macros;

mod client;
mod command;
mod from_sql;
mod query;
mod sql_read_bytes;
mod to_sql;

pub mod error;
mod result;
mod row;
mod tds;

mod sql_browser;

pub use client::{AuthMethod, Client, Config, ConfigBuilder};
pub use command::{Command, SqlTableData, SqlTableDataRow, TableValue, TableValueRow};
pub(crate) use error::Error;
pub use from_sql::{FromSql, FromSqlOwned};
pub use query::Query;
pub use result::*;
pub use row::{Column, ColumnType, QueryIdx, Row};
pub use sql_browser::SqlBrowser;
pub use tds::{
    codec::{
        AltMetaDataColumn, BaseMetaDataColumn, BulkLoadRequest, ColumnData, ColumnFlag,
        FixedLenType, IntoRow, IsolationLevel, MetaDataColumn, TokenAltMetaData, TokenAltRow,
        TokenRow, TypeInfo, TypeLength, VarLenContext, VarLenType,
    },
    collation::Collation,
    numeric,
    stream::{CommandReturnValue, CommandStream, QueryStream},
    time, xml, EncryptionLevel,
};
pub use to_sql::{IntoSql, ToSql};
pub use uuid::Uuid;

use sql_read_bytes::*;
use tds::codec::*;

/// An alias for a result that holds crate's error type as the error.
pub type Result<T> = std::result::Result<T, Error>;

pub(crate) fn get_driver_version() -> u64 {
    encode_driver_version(env!("CARGO_PKG_VERSION"))
}

/// Packs a dotted version string into the little-endian byte layout the TDS
/// login record expects: the first component in the low byte, the next in bits
/// 8..16, and so on (up to six components). Non-numeric components contribute
/// zero.
fn encode_driver_version(version: &str) -> u64 {
    version
        .splitn(6, '.')
        .enumerate()
        .fold(0u64, |acc, part| match part.1.parse::<u64>() {
            Ok(num) => acc | num << (part.0 * 8),
            // A non-numeric component contributes nothing.
            _ => acc,
        })
}

#[cfg(test)]
mod driver_version_tests {
    use super::encode_driver_version;

    #[test]
    fn packs_each_component_into_its_own_byte() {
        // 1 | 2<<8 | 3<<16 = 0x030201
        assert_eq!(encode_driver_version("1.2.3"), 0x03_02_01);
        // Distinct values per position pin the shift amounts.
        assert_eq!(encode_driver_version("4.5.6.7"), 0x07_06_05_04);
    }

    #[test]
    fn shift_moves_components_left_not_right() {
        // With `>>` instead of `<<`, `17 >> 8 == 0`, so the minor version would
        // vanish and the result would collapse to just the major (34).
        assert_eq!(encode_driver_version("34.17"), 34 | (17 << 8));
        assert_ne!(encode_driver_version("34.17"), 34);
    }

    #[test]
    fn components_are_combined_with_or_not_xor() {
        // 257 (0x101) in byte 0 shares bit 8 with `1 << 8` (0x100). OR keeps the
        // bit set (0x101); XOR would clear it (0x001), so this pins `|` vs `^`.
        assert_eq!(encode_driver_version("257.1"), 0x101);
    }

    #[test]
    fn non_numeric_components_contribute_zero() {
        assert_eq!(encode_driver_version("1.beta.3"), 1 | (3 << 16));
        assert_eq!(encode_driver_version("notaversion"), 0);
    }

    #[test]
    fn get_driver_version_encodes_the_crate_version() {
        // Pins the wrapper to the real version so a body-replacement mutant
        // (e.g. "-> 0" or "-> 1") is caught.
        assert_eq!(
            super::get_driver_version(),
            encode_driver_version(env!("CARGO_PKG_VERSION"))
        );
        assert_ne!(super::get_driver_version(), 0);
    }
}