Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v22.0.0 release #304

Merged
merged 12 commits into from
Dec 28, 2021
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mysql"
version = "21.0.2"
version = "22.0.0"
authors = ["blackbeam"]
description = "Mysql client library implemented in rust"
license = "MIT/Apache-2.0"
Expand All @@ -9,7 +9,7 @@ repository = "https://github.com/blackbeam/rust-mysql-simple"
keywords = ["database", "sql"]
exclude = ["tests/*", ".*", "Makefile"]
categories = ["database"]
edition = "2018"
edition = "2021"
build = "build.rs"

[badges.azure-devops]
Expand All @@ -27,7 +27,10 @@ debug = true
[features]
default = [
"native-tls",

# It is necessary to choose one of `flate2` backends.
"flate2/zlib",

"mysql_common/bigdecimal03",
"mysql_common/rust_decimal",
"mysql_common/time03",
Expand All @@ -46,6 +49,7 @@ time = "0.3"
[dependencies]
bufstream = "~0.1"
bytes = "1.0.1"
crossbeam = "0.8.1"
io-enum = "1.0.0"
flate2 = { version = "1.0", default-features = false }
lru = "0.7"
Expand Down
66 changes: 55 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ This crate offers:
Features:

* macOS, Windows and Linux support;
* TLS support via **nativetls** create;
* TLS support via **nativetls** or **rustls** (see the [SSL Support](#ssl-support) section);
* MySql text protocol support, i.e. support of simple text queries and text result sets;
* MySql binary protocol support, i.e. support of prepared statements and binary result sets;
* support of multi-result sets;
* support of named parameters for prepared statements;
* optional per-connection cache of prepared statements;
* support of named parameters for prepared statements (see the [Named Parameters](#named-parameters) section);
* optional per-connection cache of prepared statements (see the [Statement Cache](#statement-cache) section);
* buffer pool (see the [Buffer Pool](#buffer-pool) section);
* support of MySql packets larger than 2^24;
* support of Unix sockets and Windows named pipes;
* support of custom LOCAL INFILE handlers;
Expand Down Expand Up @@ -50,8 +51,8 @@ struct Payment {
}

let url = "mysql://root:password@localhost:3307/db_name";
let opts = Opts::from_url(url)?;
let pool = Pool::new(opts)?;

let pool = Pool::new(url)?;

let mut conn = pool.get_conn()?;

Expand Down Expand Up @@ -306,8 +307,8 @@ match unknown_val {
println!("A double precision float value: {}", from_value::<f64>(val))
}
val @ Value::Date(..) => {
use mysql::chrono::NaiveDateTime;
println!("A date value: {}", from_value::<NaiveDateTime>(val))
use time::PrimitiveDateTime;
println!("A date value: {}", from_value::<PrimitiveDateTime>(val))
}
val @ Value::Time(..) => {
use std::time::Duration;
Expand Down Expand Up @@ -455,8 +456,7 @@ let mut conn = Conn::new(get_opts())?;
let mut result = conn.query_iter("SELECT 1, 2; SELECT 3, 3.14;")?;

let mut sets = 0;
while let Some(result_set) = result.next_set() {
let result_set = result_set?;
while let Some(result_set) = result.current_set() {
sets += 1;

println!("Result set columns: {:?}", result_set.columns());
Expand Down Expand Up @@ -598,13 +598,17 @@ Statement cache is completely disabled if `stmt_cache_size` is zero.
#### Named parameters

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.
One should use `:name` as a placeholder syntax for a named parameter. Named parameters uses
the following naming convention:

* parameter name must start with either `_` or `a..z`
* parameter name may continue with `_`, `a..z` and `0..9`

Named parameters may be repeated within the statement, e.g `SELECT :foo, :foo` will require
a single named parameter `foo` that will be repeated on the corresponding positions during
statement execution.

One should use the `params!` macro to build a parameters for execution.
One should use the `params!` macro to build parameters for execution.

**Note:** Positional and named parameters can't be mixed within the single statement.

Expand All @@ -626,6 +630,23 @@ assert_eq!((foo, 13, foo), val_42);
assert_eq!((13, foo, 13), val_13);
```

#### Buffer pool

Crate uses the global lock-free buffer pool for the purpose of IO and data serialization/deserialization,
that helps to avoid allocations for basic scenarios. You can control it's characteristics using
the following environment variables:

* `RUST_MYSQL_BUFFER_POOL_CAP` (defaults to 128) – controls the pool capacity. Dropped buffer will
be immediately deallocated if the pool is full.

**Note:** it might be the case, that you don't need the pooling (say you are using jemalloc).
It's possible to disable the pool by setting the `RUST_MYSQL_BUFFER_POOL_CAP` environment
variable to `0`.

* `RUST_MYSQL_BUFFER_SIZE_CAP` (defaults to 4MiB) – controls the maximum capacity of a buffer
stored in the pool. Capacity of a dropped buffer will be shrinked to this value when buffer
is returning to the pool.

#### `BinQuery` and `BatchQuery` traits.

`BinQuery` and `BatchQuery` traits covers the set of `Queryable::exec*` methods from
Expand Down Expand Up @@ -697,6 +718,29 @@ These methods will consume only the first result set, other result sets will be
The trait also defines the `exec_batch` function, which is a helper for batch statement
execution.

### SSL Support

SSL support comes in two flavors:

1. Based on **native-tls** – this is the default option, that usually works without pitfalls
(see the `native-tls` crate feature).
2. Based on **rustls** – TLS backend written in Rust. Please use the `rustls-tls` crate feature
to enable:

```toml
[dependencies]
mysql = { version = "*", no-default-features = true, features = ["rustls-tls"] }
# Please note, that the previous line disables default mysql features,
# so now we have to choose the flate2 backend (this is necessary):
flate2 = { version = "1.0", no-default-features = true, features = ["zlib"] }
```

Please also note a few things about this backend:

* 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.

[crate docs]: https://docs.rs/mysql
[mysql_common docs]: https://docs.rs/mysql_common
[max_prepared_stmt_count]: https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_prepared_stmt_count
Expand Down
12 changes: 6 additions & 6 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
SSL=false COMPRESS=false cargo test --no-default-features --features flate2/zlib,mysql_common/time03
env:
RUST_BACKTRACE: 1
DATABASE_URL: mysql://root:root@127.0.0.1:3306/mysql
DATABASE_URL: mysql://root:root@localhost:3306/mysql
displayName: Run tests

- job: "TestBasicMacOs"
Expand Down Expand Up @@ -98,7 +98,7 @@ jobs:
SSL=false COMPRESS=false cargo test --no-default-features --features flate2/zlib,mysql_common/time03
env:
RUST_BACKTRACE: 1
DATABASE_URL: mysql://root@127.0.0.1/mysql
DATABASE_URL: mysql://root@localhost/mysql
displayName: Run tests

- job: "TestBasicWindows"
Expand Down Expand Up @@ -149,7 +149,7 @@ jobs:
SSL=false COMPRESS=false cargo test --no-default-features --features flate2/zlib,mysql_common/time03
env:
RUST_BACKTRACE: 1
DATABASE_URL: mysql://root:password@127.0.0.1/mysql
DATABASE_URL: mysql://root:password@localhost/mysql
displayName: Run tests

- job: "TestMySql"
Expand Down Expand Up @@ -186,7 +186,7 @@ jobs:
docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable"
displayName: Install Rust in docker
- bash: |
if [[ "5.6" != "$(DB_VERSION)" ]]; then SSL=true; else DATABASE_URL="mysql://root2:password@127.0.0.1/mysql?secure_auth=false"; fi
if [[ "5.6" != "$(DB_VERSION)" ]]; then SSL=true; else DATABASE_URL="mysql://root2:password@localhost/mysql?secure_auth=false"; fi
docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL cargo test"
docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL COMPRESS=true cargo test"
docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=$SSL cargo test"
Expand All @@ -199,7 +199,7 @@ jobs:
docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=false COMPRESS=false cargo test --no-default-features --features flate2/zlib,mysql_common/time03"
env:
RUST_BACKTRACE: 1
DATABASE_URL: mysql://root:password@127.0.0.1/mysql
DATABASE_URL: mysql://root:password@localhost/mysql
displayName: Run tests in Docker

- job: "TestMariaDb"
Expand Down Expand Up @@ -267,5 +267,5 @@ jobs:
docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=false COMPRESS=false cargo test --no-default-features --features flate2/zlib,mysql_common/time03"
env:
RUST_BACKTRACE: 1
DATABASE_URL: mysql://root:password@127.0.0.1/mysql
DATABASE_URL: mysql://root:password@localhost/mysql
displayName: Run tests in Docker
92 changes: 48 additions & 44 deletions src/buffer_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,65 @@
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

use std::{
mem::replace,
ops::Deref,
sync::{Arc, Mutex},
};
use crossbeam::queue::ArrayQueue;

use std::{mem::replace, ops::Deref, sync::Arc};

const DEFAULT_MYSQL_BUFFER_POOL_CAP: usize = 128;
const DEFAULT_MYSQL_BUFFER_SIZE_CAP: usize = 4 * 1024 * 1024;

#[derive(Debug)]
pub struct BufferPool {
pool_cap: usize,
struct Inner {
buffer_cap: usize,
pool: Mutex<Vec<Vec<u8>>>,
pool: ArrayQueue<Vec<u8>>,
}

impl BufferPool {
pub fn new() -> Self {
let pool_cap = std::env::var("MYSQL_BUFFER_POOL_CAP")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(128_usize);

let buffer_cap = std::env::var("MYSQL_BUFFER_SIZE_CAP")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(4 * 1024 * 1024);

Self {
pool: Default::default(),
pool_cap,
buffer_cap,
}
}

pub fn get(self: &Arc<Self>) -> PooledBuf {
let mut buf = self.pool.lock().unwrap().pop().unwrap_or_default();
impl Inner {
fn get(self: &Arc<Self>) -> PooledBuf {
let mut buf = self.pool.pop().unwrap_or_default();

// SAFETY:
// 1. OK – 0 is always within capacity
// 2. OK - nothing to initialize
unsafe { buf.set_len(0) }

PooledBuf(buf, self.clone())
PooledBuf(buf, Some(self.clone()))
}

fn put(self: &Arc<Self>, mut buf: Vec<u8>) {
if buf.len() > self.buffer_cap {
// TODO: until `Vec::shrink_to` stabilization
fn put(&self, mut buf: Vec<u8>) {
buf.shrink_to(self.buffer_cap);
let _ = self.pool.push(buf);
}
}

// SAFETY:
// 1. OK – new_len <= capacity
// 2. OK - 0..new_len is initialized
unsafe { buf.set_len(self.buffer_cap) }
buf.shrink_to_fit();
}
/// Smart pointer to a buffer pool.
#[derive(Debug, Clone)]
pub struct BufferPool(Option<Arc<Inner>>);

impl BufferPool {
pub fn new() -> Self {
let pool_cap = std::env::var("RUST_MYSQL_BUFFER_POOL_CAP")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(DEFAULT_MYSQL_BUFFER_POOL_CAP);

let mut pool = self.pool.lock().unwrap();
if pool.len() < self.pool_cap {
pool.push(buf);
let buffer_cap = std::env::var("RUST_MYSQL_BUFFER_SIZE_CAP")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(DEFAULT_MYSQL_BUFFER_SIZE_CAP);

Self((pool_cap > 0).then(|| {
Arc::new(Inner {
buffer_cap,
pool: ArrayQueue::new(pool_cap),
})
}))
}

pub fn get(self: &Arc<Self>) -> PooledBuf {
match self.0 {
Some(ref inner) => inner.get(),
None => PooledBuf(Vec::new(), None),
}
}
}
Expand All @@ -74,7 +76,7 @@ impl Default for BufferPool {
}

#[derive(Debug)]
pub struct PooledBuf(Vec<u8>, Arc<BufferPool>);
pub struct PooledBuf(Vec<u8>, Option<Arc<Inner>>);

impl AsMut<Vec<u8>> for PooledBuf {
fn as_mut(&mut self) -> &mut Vec<u8> {
Expand All @@ -92,6 +94,8 @@ impl Deref for PooledBuf {

impl Drop for PooledBuf {
fn drop(&mut self) {
self.1.put(replace(&mut self.0, vec![]))
if let Some(ref inner) = self.1 {
inner.put(replace(&mut self.0, vec![]));
}
}
}
17 changes: 15 additions & 2 deletions src/conn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,19 @@ mod test {
)
.unwrap();
conn.exec_drop("SELECT * FROM TEST_TABLE", ()).unwrap();

let mut query_result = conn
.query_iter(
r"
SELECT * FROM TEST_TABLE;
INSERT INTO TEST_TABLE (name) VALUES ('one');
DO 0;",
)
.unwrap();

while let Some(result) = query_result.current_set() {
result.affected_rows();
}
}

#[test]
Expand Down Expand Up @@ -1739,9 +1752,9 @@ mod test {
}
let mut result = conn.query_iter("SELECT 1; SELECT 2; SELECT 3;").unwrap();
let mut i = 0;
while let Some(result_set) = result.next_set() {
while let Some(result_set) = result.current_set() {
i += 1;
for row in result_set.unwrap() {
for row in result_set {
match i {
1 => assert_eq!(row.unwrap().unwrap(), vec![Bytes(b"1".to_vec())]),
2 => assert_eq!(row.unwrap().unwrap(), vec![Bytes(b"2".to_vec())]),
Expand Down
2 changes: 1 addition & 1 deletion src/conn/opts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ impl OptsBuilder {
/// - stmt_cache_size = Number of prepared statements cached on the client side (per connection)
/// - secure_auth = Disable `mysql_old_password` auth plugin
///
/// Login .cnf file parsing lib https://github.com/rjcortese/myloginrs returns a HashMap for client configs
/// Login .cnf file parsing lib <https://github.com/rjcortese/myloginrs> returns a HashMap for client configs
///
/// **Note:** You do **not** have to use myloginrs lib.
pub fn from_hash_map(mut self, client: &HashMap<String, String>) -> Result<Self, UrlError> {
Expand Down
4 changes: 2 additions & 2 deletions src/conn/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,8 @@ where
let params = params.into();
let meta = conn._execute(&*statement, params)?;
let mut query_result = QueryResult::<Binary>::new((&mut *conn).into(), meta);
while let Some(result_set) = query_result.next_set() {
for row in result_set? {
while let Some(result_set) = query_result.current_set() {
for row in result_set {
row?;
}
}
Expand Down
Loading