Skip to content

Commit

Permalink
Merge pull request #304 from blackbeam/v22.0.0-release
Browse files Browse the repository at this point in the history
v22.0.0 release
  • Loading branch information
blackbeam authored Dec 28, 2021
2 parents f2db414 + 2dce4ae commit 22725fe
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 108 deletions.
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

0 comments on commit 22725fe

Please sign in to comment.