prisma/tiberius

A native Microsoft SQL Server (TDS) client for Rust

A perfect implementation of the TDS protocol

Tiberius

A native Microsoft SQL Server (TDS) client for Rust.

Goals

  • A perfect implementation of the TDS protocol.
  • Asynchronous network IO.
  • Independent of the network protocol.
  • Support for latest versions of Linux, Windows and macOS.

Non-goals

  • Connection pooling (use bb8, mobc, deadpool or any of the other asynchronous connection pools)
  • Query building
  • Object-relational mapping

Supported SQL Server versions

Version Support level Notes
2019 Tested on CI
2017 Tested on CI
2016 Should work
2014 Should work
2012 Should work
2008 Should work
2005 Should work With feature flag tds73 disabled.

Feature flags

Flag Description Default
tds73 Support for new date and time types in TDS version 7.3. Disable if using version 7.2. enabled
native-tls Use operating system's TLS libraries for traffic encryption. enabled
chrono Read and write date and time values using chrono's types. (for greenfield, using time instead of chrono is recommended) disabled
time Read and write date and time values using time crate types. disabled
rust_decimal Read and write numeric/decimal values using rust_decimal's Decimal. disabled
bigdecimal Read and write numeric/decimal values using bigdecimal's BigDecimal. disabled
sql-browser-async-std SQL Browser implementation for the TcpStream of async-std. disabled
sql-browser-tokio SQL Browser implementation for the TcpStream of Tokio. disabled
sql-browser-smol SQL Browser implementation for the TcpStream of smol. disabled
integrated-auth-gssapi Support for using Integrated Auth via GSSAPI disabled
rustls Use the builtin TLS implementation from rustls instead of linking to the operating system implementation for traffic encryption. disabled

Supported protocols

Tiberius does not rely on any protocol when connecting to an SQL Server instance. Instead the Client takes a socket that implements the AsyncRead and AsyncWrite traits from the futures-rs crate.

Currently there are good async implementations for TCP in the async-std, Tokio and Smol projects. To be able to use them together with Tiberius on Windows platforms with SQL Server, TCP protocol should be enabled in the server settings (disabled by default). In the official Docker image TCP is is enabled by default.

Named pipes should work by using the NamedPipeClient from the latest Tokio versions.

The shared memory protocol is not documented and seems there are no Rust crates implementing it.

Encryption (TLS/SSL)

Tiberius can be set to use two different implementations of TLS connection encryption. By default it uses native-tls, linking to the TLS library provided by the operating system. This is a good practice and in case of security vulnerabilities, upgrading the system libraries fixes the vulnerability in Tiberius without a recompilation. On Linux we link against OpenSSL, on Windows against schannel and on macOS against Security Framework.

Alternatively one can use the rustls feature flag to use the Rust native TLS implementation. This way there are no dynamic dependencies to the system. This might be useful in certain installations, but requires a rebuild to update to a new TLS version. For some reasons the Security Framework on macOS does not work with SQL Server TLS settings, and on Apple platforms if needing TLS it is recommended to use rustls instead of native-tls.

The crate can also be compiled without TLS support, but not with both features enabled at the same time.

Tiberius has three runtime encryption settings:

Encryption level Description
Required All traffic is encrypted. (default)
Off Only the login procedure is encrypted.
NotSupported None of the traffic is encrypted.

The encryption levels can be set when connecting to the database.

Integrated Authentication (TrustedConnection) on *nix

With the integrated-auth-gssapi feature enabled, the crate requires the GSSAPI/Kerberos libraries/headers installed:

  • CentOS
  • Arch
  • Debian (you need the -dev packages to build)
  • Ubuntu
  • NixOS: Run nix-shell shell.nix on the repository root.
  • Mac: as of version 0.4.2 the libgssapi crate used for this feature now uses Apple's GSS Framework which ships with MacOS 10.14+.

Additionally, your runtime system will need to be trusted by and configured for the Active Directory domain your SQL Server is part of. In particular, you'll need to be able to get a valid TGT for your identity, via kinit or a keytab. This setup varies by environment and OS, but your friendly network/system administrator should be able to help figure out the specifics.

Redirects

With certain Azure firewall settings, a login might return Error::Routing { host, port }. This means the user must create a new TcpStream to the given address, and connect again.

A simple connection procedure would then be:

Security

If you have a security issue to report, please contact us at [email protected]

Issues

Collection of the latest Issues

bbigras

bbigras

Comment Icon0

I got some f64::NAN from a source I don't control.

I got some errors when inserting into a decimal(19, 4) (null allowed) column.

error:

I have no idea how other SQL crates handle this.

Should tiberius handle NaN?

yatesco

yatesco

Comment Icon4

Hiya, I haven't debugged this yet, but I wanted to report it ASAP. Upgrading from 0.8 to 0.9 results in every connection failing and timing out.

Cargo.toml:

My code:

My connection string: server=tcp:127.0.0.1,1433;encrypt=false;TrustServerCertificate=true;user=XXX;password=YYY;database=AAAA;

Any pointers? Thanks!

BezBIS

BezBIS

Comment Icon0

I've run into an issue with case sensitivity on column names with row.get(). Many of the tables in the database I'm accessing share common column names in capitalised snake case (e.g. CHANGED_BY). These shared columns are often part of a data structure, such as a location with a LAT and LONG field.

One of the tables, unfortunately, has left the last word of a couple of column headings in lower case (e.g. CHANGED_by). Rather than rewrite my logic for this single table, or alter the databse itself (over 3 million records) it would be nice if there was a case insensitive version of row.get() to bypass situations like this where the databse structure is not as clean as would be hoped.

JasonCookSyncadd

JasonCookSyncadd

Comment Icon2

For crafting validation of fields, column or ResultMetadata should include the nullable, size, and scale attributes. This is important even if returned resultset does not contain any rows of data.

While useful in all kinds of cases, these properties are especially useful for validation, BI, ETL, and migration tools.

Nullable would be set to true on any column that allows null, and false for any column that does not allow null.

Example use cases for size and scale: for Varchar and NVarchar, size should be populated with the varchar(size), and scale would either be zero or None. for Numeric and Decimal, size should be the precision and scale should contain the number of digits to the right of the decimal. for Bitn, Intn, Floatn, Datetimen, size should be the number of bits (or bytes) of the column, and scale would either be zero or None.

If any are complex to implement, any of these attributes implemented would be helpful.

efendioglu67

efendioglu67

Comment Icon2

Hello friends,

when I select over and table I get an tiberius::ColumnType::Intn for bigint and int from a column in my result. How can I distinguish the type for getting my value with: let val = row.get::<i32,_>(x);

When my original value from my db is bigint I get an error when I specify an i32 on get<>. Why is it put through Intn for both types int and bigint??

Ankerbot

Ankerbot

Comment Icon0

https://github.com/prisma/tiberius/blob/0f3bffdbabc42233850682ff1767840edc9ccba4/src/row.rs#L392

Hey, thanks for tiberius!

In try_get in row.rs:390 there is the possibility to panic in the unwrap, which shouldn't happen.

I got it by doing

I would expect it to return some kind of error instead of panic-ing, a generic one would be alright.

I feel like there is something else going wrong here, because normally the check for row bounds should have already returned an error.

dtheodor

dtheodor

Comment Icon4

This is more of a question than an issue. Is there a way to check if the TCP connection is not closed? As far as I can tell the TcpStream is not exposed in any way from the client and thus its methods are not usable

adamrybak

adamrybak

Comment Icon0

I would like the ability to pull in row data directly into an owned struct, currently not possible with row.get as there is only support for &str and &[u8] in the FromSql trait.

Consider the following code as example. The commented lines are not possible with the current main branch.

zhoujun681

zhoujun681

Comment Icon1

"Error forming TLS connection: a certificate chain processed, but terminated in a root certificate which is not trusted。 (os error -2146762487)" error occured when i compile example from github/tiberius. How can i sloved it?

servonlewis

servonlewis

Comment Icon7

Hey guys,

Is there any example of how to create an mssql connection pool, rather than initiating a new connection per query?

If so, how can I do it?

kesavkolla

kesavkolla

Comment Icon4

Can we support batch reads of the results? I typically deal with millions of rows from query output. It would be great if there is a way to batch the results so that I can start processing as soon as a batch is available.

joshbenaron

joshbenaron

Comment Icon2

Hey,

This may not be a Tiberius specific issue but I have a Rust server hosted on GCP Serverless service. I've discovered all queries using COUNT(...) function cause it to fail. So I get an anonymous error in GCP which leads to the service having to automatically restarting. I was wondering if you would maybe have an idea of the underlying issue. Whether it's some form of unsupported feature in the cloud?

pimeys

pimeys

help wanted
Comment Icon8

We could maybe have a nice interface for people using serde structs in their code. Let's see a few examples:

Now using the struct, we could deserialize rows directly to them in an efficient manner:

The question is, should Tiberius do this, or should it be let for higher-level crates, such as Diesel?

mcamou

mcamou

help wanted
Comment Icon13

Running INSERT INTO table VALUES(...) is really slow especially when e.g. inserting against an Azure server. You can batch inserts with INSERT INTO table VALUES(({},{}),({},{})), but that is limited to 1000 values. You can also use INSERT INTO table SELECT a,b FROM (VALUES {},{},{},{}))sub(a,b) as mentioned in https://github.com/steffengy/tiberius/issues/93, but you are limited to 2100 values in the query.

A solution to this would be to implement something similar to what Tedious (NodeJS library) implements: https://tediousjs.github.io/tedious/bulk-load.html

1Dragoon

1Dragoon

Comment Icon7

I'm largely a noob to rust (coming from a c# background, been doing rust for 4 weeks) but I'm trying to start with the basics, so I'm doing something simple like dump part of a table to a CSV file. So far I can connect to the db and create a string representing a CSV header:

No problem.

However when I try to read the values from each row, that's where I get lost. I noticed the get() method and tried to serialize from that, but I can't figure out how to get a string out of that. Any assistance? I know the below code doesn't work because it needs me to specify a type, but I'm not quite sure how.

Where I'm stuck is the rowtext.push() bit. I imagine that I'm in want of a superfish type representation here, but I can't figure it out from the documentation. Any help?

pimeys

pimeys

tech/engines
Comment Icon1

This has not bubbled up yet, but I highly suspect we're going to hit problems when cancelling futures reading or writing data with Tiberius.

This means, if we use select functionality between a Tiberius future with another, such as a timer, and if we're just reading or writing data to the wire, this operation will be stopped when the timer is ready. At this point the wire is in a dysfunctional state, where either it has partial data from the previous request still awaiting, or we wrote some of the data, but not all.

This means if implementing a service, that for example reads input from a field to perform a search, that is cancelled when the user presses more keys to replace the query with a new one, the connection probably ends into a state where we must reconnect for it to work again.

Some ways of fixing this:

  • Mark a connection as dirty until all data has been read and written
  • Implement the streams so they just peek the data until we know the headers, and only then reading the full packet
  • And then, somehow before every new query, check the wire status and if its dirty, find a way to clean it before triggering a new query.
pimeys

pimeys

kind/improvement
Comment Icon0

SQL Server supports Column Encryption (CEK), and we should probably implement some kind of support for it in Tiberius.

The client should take the needed keys, and transparently encrypt/decrypt columns if needed.

Information - Updated Jun 02, 2022

Stars: 147
Forks: 75
Issues: 34

Repositories & Extras

macOS/iOS Security framework for Rust

MIT license (LICENSE-MIT or

macOS/iOS Security framework for Rust
Actix

2.6K

Curated examples using the Actix ecosystem

Zero2prod : Source code of zero to production series Triox : A free file hosting server that focuses on speed, reliability and security

Curated examples using the Actix ecosystem

binserve :zap::crab:

A blazingly fast static web server with routing, templating, and security in a single binary you can set up with zero code

binserve :zap::crab:

libdiffuzz: security-oriented alternative to Memory Sanitizer

This is a drop-in replacement for OS memory allocator that can be used to detect uses of uninitialized memory

libdiffuzz: security-oriented alternative to Memory Sanitizer

osu! server written in Rust

Fully asynchronous, high concurrency, high performance, and high security

osu! server written in Rust

Transparent endpoint security

Block and detect advanced attacks

Transparent endpoint security

Rust Language Security

execrices: RUSTSEC-2021-0001

Rust Language Security

No security audit has been performed

There are currently 3 interesting things

No security audit has been performed

security-keys-rust

Many thanks to the authors of the openpgp-card Rust crate

security-keys-rust

Owlyshield open source security platform

An OSS security platform written in rust with security threat detection

Owlyshield open source security platform

No security audit has been performed

There are currently 3 interesting things

No security audit has been performed

No security audit has been performed

There are currently 5 interesting things

No security audit has been performed
Facebook Instagram Twitter GitHub Dribbble
Privacy