- Notifications
You must be signed in to change notification settings - Fork125
Asyncronous Rust Mysql driver based on Tokio.
License
Apache-2.0, MIT licenses found
Licenses found
blackbeam/mysql_async
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Tokio based asynchronous MySql client library for The Rust Programming Language.
The library is hosted oncrates.io.
[dependencies]mysql_async ="<desired version>"
By default there are only two features enabled:
flate2/zlib
— choosing flate2 backend is mandatoryderive
— see"Derive Macros" section inmysql_common
docs
minimal
– enables only necessary features (at the moment the only necessary featureisflate2
backend). Enables:- `flate2/zlib"
Example:
[dependencies]mysql_async = {version ="*",default-features =false,features = ["minimal"]}
minimal-rust
- same asminimal
but with rust-based flate2 backend. Enables:flate2/rust_backend
default
– enables the following set of features:flate2/zlib
derive
default-rustls
– default set of features with TLS viarustls/aws-lc-rs
default-rustls-ring
– default set of features with TLS viarustls/ring
Example:
[dependencies]mysql_async = {version ="*",default-features =false,features = ["default-rustls"] }
native-tls-tls
– enables TLS vianative-tls
Example:
[dependencies]mysql_async = {version ="*",default-features =false,features = ["minimal","native-tls-tls"] }
rustls-tls
- enables rustls TLS backend with no provider. You should enable oneof existing providers usingaws-lc-rs
orring
features:Example:
[dependencies]mysql_async = {version ="*",default-features =false,features = ["minimal-rust","rustls-tls","ring"] }
tracing
– enables instrumentation viatracing
package.Primary operations (
query
,prepare
,exec
) are instrumented atINFO
level.Remaining operations, incl.get_conn
, are instrumented atDEBUG
level.Also atDEBUG
, the SQL queries and parameters are added to thequery
,prepare
andexec
spans. Also some internal queries are instrumented atTRACE
level.Example:
[dependencies]mysql_async = {version ="*",features = ["tracing"] }
binlog
- enables binlog-related functionality. Enables:- `mysql_common/binlog"
Proxied features (see`mysql_common`` fatures)
derive
– enablesmysql_common/derive
featurechrono
= enablesmysql_common/chrono
featuretime
= enablesmysql_common/time
featurebigdecimal
= enablesmysql_common/bigdecimal
featurerust_decimal
= enablesmysql_common/rust_decimal
featurefrunk
= enablesmysql_common/frunk
feature
SSL support comes in two flavors:
Based on native-tls – this is the default option, that usually works without pitfalls(see the
native-tls-tls
crate feature).Based on rustls – TLS backend written in Rust (see the
rustls-tls
crate feature).Please also note a few things about rustls:
- it will fail if you'll try to connect to the server by its IP address,hostname is required;
- it, most likely, won't work on windows, at least with default server certs,generated by the MySql installer.
There is a set of url-parameters supported by the driver (see documentation on [Opts
]).
use mysql_async::prelude::*;#[derive(Debug,PartialEq,Eq,Clone)]structPayment{customer_id:i32,amount:i32,account_name:Option<String>,}#[tokio::main]asyncfnmain() ->Result<()>{let payments =vec![Payment{ customer_id:1, amount:2, account_name:None},Payment{ customer_id:3, amount:4, account_name:Some("foo".into())},Payment{ customer_id:5, amount:6, account_name:None},Payment{ customer_id:7, amount:8, account_name:None},Payment{ customer_id:9, amount:10, account_name:Some("bar".into())},];let database_url =/* ... */ #get_opts();let pool = mysql_async::Pool::new(database_url);letmut conn = pool.get_conn().await?;// Create a temporary tabler"CREATE TEMPORARY TABLE payment ( customer_id int not null, amount int not null, account_name text )".ignore(&mut conn).await?;// Save paymentsr"INSERT INTO payment (customer_id, amount, account_name) VALUES (:customer_id, :amount, :account_name)".with(payments.iter().map(|payment|params!{"customer_id" => payment.customer_id,"amount" => payment.amount,"account_name" => payment.account_name.as_ref(),})).batch(&mut conn).await?;// Load payments from the database. Type inference will work here.let loaded_payments ="SELECT customer_id, amount, account_name FROM payment".with(()).map(&mut conn, |(customer_id, amount, account_name)|Payment{ customer_id, amount, account_name}).await?;// Dropped connection will go to the pooldrop(conn);// The Pool must be disconnected explicitly because// it's an asynchronous operation. pool.disconnect().await?;assert_eq!(loaded_payments, payments);// the async fn returns Result, soOk(())}
The [Pool
] structure is an asynchronous connection pool.
Please note:
- [
Pool
] is a smart pointer – each clone will point to the same pool instance. - [
Pool
] isSend + Sync + 'static
– feel free to pass it around. - use [
Pool::disconnect
] to gracefuly close the pool. ⚠️ [Pool::new
] is lazy and won't assert server availability.
[Conn::start_transaction
] is a wrapper, that starts withSTART TRANSACTION
and ends withCOMMIT
orROLLBACK
.
Dropped transaction will be implicitly rolled back if it wasn't explicitlycommitted or rolled back. Note that this behaviour will be triggered by a pool(on conn drop) or by the next query, i.e. may be delayed.
API won't allow you to run nested transactions because some statements causesan implicit commit (START TRANSACTION
is one of them), so this behavioris chosen as less error prone.
This enumeration represents the raw value of a MySql cell. Library offers conversion betweenValue
and different rust types viaFromValue
trait described below.
This trait is reexported frommysql_common create. Please refer to itscrate docs for the list of supported conversions.
Trait offers conversion in two flavours:
from_value(Value) -> T
- convenient, but panicking conversion.Note, that for any variant of
Value
there exist a type, that fully covers its domain,i.e. for any variant ofValue
there existT: FromValue
such thatfrom_value
will neverpanic. This means, that if your database schema is known, than it's possible to write yourapplication using onlyfrom_value
with no fear of runtime panic.Also note, that some convertions may fail even though the type seem sufficient,e.g. in case of invalid dates (seesql mode).
from_value_opt(Value) -> Option<T>
- non-panicking, but less convenient conversion.This function is useful to probe conversion in cases, where source database schemais unknown.
MySql text protocol is implemented in the set ofQueryable::query*
methodsand in the [prelude::Query
] trait if query is [prelude::AsQuery
].It's useful when your query doesn't have parameters.
Note: All values of a text protocol result set will be encoded as strings by the server,sofrom_value
conversion may lead to additional parsing costs.
MySql binary protocol is implemented in the set ofexec*
methods,defined on the [prelude::Queryable
] trait and in the [prelude::Query
]trait if query is [QueryWithParams
]. Prepared statements is the only way topass rust value to the MySql server. MySql uses?
symbol as a parameter placeholder.
Note: it's only possible to use parameters where a single MySql valueis expected, i.e. you can't execute something likeSELECT ... WHERE id IN ?
with a vector as a parameter. You'll need to build a query that looks likeSELECT ... WHERE id IN (?, ?, ...)
and to pass each vector element asa parameter.
MySql itself doesn't have named parameters support, so it's implemented on the client side.One should use:name
as a placeholder syntax for a named parameter. Named parameters usesthe following naming convention:
- parameter name must start with either
_
ora..z
- parameter name may continue with
_
,a..z
and0..9
Note: this rules mean that, say, the statmentSELECT :fooBar
will be translatedtoSELECT ?Bar
so please be careful.
Named parameters may be repeated within the statement, e.gSELECT :foo, :foo
will requirea single named parameterfoo
that will be repeated on the corresponding positions duringstatement execution.
One should use theparams!
macro to build parameters for execution.
Note: Positional and named parameters can't be mixed within the single statement.
In MySql each prepared statement belongs to a particular connection and can't be executedon another connection. Trying to do so will lead to an error. The driver won't tie statementto its connection in any way, but one can look on to the connection id, containedin the [Statement
] structure.
Warning: You should be aware ofSecurity Considerations for LOAD DATA LOCAL.
There are two flavors of LOCAL INFILE handlers –global andlocal.
I case of a LOCAL INFILE request from the server the driver will try to find a handler for it:
- It'll try to uselocal handler installed on the connection, if any;
- It'll try to useglobal handler, specified via [
OptsBuilder::local_infile_handler
],if any; - It will emit [
LocalInfileError::NoHandler
] if no handlers found.
The purpose of a handler (local orglobal) is to return [InfileData
].
See [prelude::GlobalHandler
].
Simply speaking theglobal handler is an async function that takes a file name (as&[u8]
)and returnsResult<InfileData>
.
You can set it up using [OptsBuilder::local_infile_handler
]. Server will use it if there is nolocal handler installed for the connection. This handler might be called multiple times.
Examles:
- [
WhiteListFsHandler
] is aglobal handler. - Every
T: Fn(&[u8]) -> BoxFuture<'static, Result<InfileData, LocalInfileError>>
is aglobal handler.
Simply speaking thelocal handler is a future, that returnsResult<InfileData>
.
This is a one-time handler – it's consumed after use. You can set it up using[Conn::set_infile_handler
]. This handler have priority overglobal handler.
Worth noting:
impl Drop for Conn
will clearlocal handler, i.e. handler will be removed whenconnection is returned to aPool
.- [
Conn::reset
] will clearlocal handler.
Example:
#let pool = mysql_async::Pool::new(database_url);letmut conn = pool.get_conn().await?;"CREATE TEMPORARY TABLE tmp (id INT, val TEXT)".ignore(&mut conn).await?;// We are going to call `LOAD DATA LOCAL` so let's setup a one-time handler.conn.set_infile_handler(asyncmove{// We need to return a stream of `io::Result<Bytes>`Ok(stream::iter([Bytes::from("1,a\r\n"),Bytes::from("2,b\r\n3,c")]).map(Ok).boxed())});let result =r#"LOAD DATA LOCAL INFILE 'whatever' INTO TABLE `tmp` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\r\n'"#.ignore(&mut conn).await;match result{Ok(()) =>(),Err(Error::Server(ref err))if err.code ==1148 =>{// The used command is not allowed with this MySQL versionreturnOk(());},Err(Error::Server(ref err))if err.code ==3948 =>{// Loading local data is disabled;// this must be enabled on both the client and the serverreturnOk(());} e @Err(_) => e.unwrap(),}// Now let's verify the resultlet result:Vec<(u32,String)> = conn.query("SELECT * FROM tmp ORDER BY id ASC").await?;assert_eq!( result, vec![(1,"a".into()),(2,"b".into()),(3,"c".into())]);drop(conn);pool.disconnect().await?;
Tests uses followin environment variables:
DATABASE_URL
– defaults tomysql://root:password@127.0.0.1:3307/mysql
COMPRESS
– set to1
ortrue
to enable compression for testsSSL
– set to1
ortrue
to enable TLS for tests
You can run a test server using doker. Please note that params relatedto max allowed packet, local-infile and binary logging are requiredto properly run tests (please refer toazure-pipelines.yml
):
docker run -d --name container \ -v`pwd`:/root \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD=password \ mysql:8.0 \ --max-allowed-packet=36700160 \ --local-infile \ --log-bin=mysql-bin \ --log-slave-updates \ --gtid_mode=ON \ --enforce_gtid_consistency=ON \ --server-id=1
Availablehere
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE orhttps://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT orhttps://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionallysubmitted for inclusion in the work by you, as defined in the Apache-2.0license, shall be dual licensed as above, without any additional terms orconditions.
About
Asyncronous Rust Mysql driver based on Tokio.