use std::fmt;
use std::sync::{Arc, Mutex, Condvar};
use std::time::Duration as StdDuration;
use time::{Duration, SteadyTime};
use super::IsolationLevel;
use super::Transaction;
use super::super::error::{Error, DriverError};
use super::super::value::Params;
use super::{Conn, Opts, Stmt, QueryResult};
use super::super::error::Result as MyResult;
#[derive(Debug)]
struct InnerPool {
opts: Opts,
pool: Vec<Conn>,
min: usize,
max: usize,
count: usize
}
impl InnerPool {
fn new(min: usize, max: usize, opts: Opts) -> MyResult<InnerPool> {
if min > max || max == 0 {
return Err(Error::DriverError(DriverError::InvalidPoolConstraints));
}
let mut pool = InnerPool {
opts: opts,
pool: Vec::with_capacity(max),
max: max,
min: min,
count: 0
};
for _ in 0..min {
try!(pool.new_conn());
}
Ok(pool)
}
fn new_conn(&mut self) -> MyResult<()> {
match Conn::new(self.opts.clone()) {
Ok(conn) => {
self.pool.push(conn);
self.count += 1;
Ok(())
},
Err(err) => Err(err)
}
}
}
#[derive(Clone)]
pub struct Pool(Arc<(Mutex<InnerPool>, Condvar)>);
impl Pool {
pub fn new<T: Into<Opts>>(opts: T) -> MyResult<Pool> {
Pool::new_manual(10, 100, opts)
}
pub fn new_manual<T: Into<Opts>>(min: usize, max: usize, opts: T) -> MyResult<Pool> {
let pool = try!(InnerPool::new(min, max, opts.into()));
Ok(Pool(Arc::new((Mutex::new(pool), Condvar::new()))))
}
pub fn get_conn(&self) -> MyResult<PooledConn> {
let &(ref inner_pool, ref condvar) = &*self.0;
let mut pool = match inner_pool.lock() {
Ok(mutex) => mutex,
_ => return Err(Error::DriverError(DriverError::PoisonedPoolMutex)),
};
loop {
if pool.pool.is_empty() {
if pool.count < pool.max {
match pool.new_conn() {
Ok(()) => break,
Err(err) => return Err(err),
}
} else {
pool = match condvar.wait(pool) {
Ok(mutex) => mutex,
_ => return Err(Error::DriverError(DriverError::PoisonedPoolMutex)),
}
}
} else {
break;
}
}
let mut conn = pool.pool.pop().unwrap();
if !conn.ping() {
try!(conn.reset());
}
Ok(PooledConn {pool: self.clone(), conn: Some(conn)})
}
pub fn try_get_conn(&self, timeout_ms: u32) -> MyResult<PooledConn> {
let start = SteadyTime::now();
let timeout = Duration::milliseconds(timeout_ms as i64);
let std_timeout = StdDuration::from_millis(timeout_ms as u64);
let &(ref inner_pool, ref condvar) = &*self.0;
let mut pool = match inner_pool.lock() {
Ok(mutex) => mutex,
_ => return Err(Error::DriverError(DriverError::PoisonedPoolMutex)),
};
loop {
if pool.pool.is_empty() {
if pool.count < pool.max {
match pool.new_conn() {
Ok(()) => break,
Err(err) => return Err(err),
}
} else {
if SteadyTime::now() - start > timeout {
return Err(DriverError::Timeout.into());
}
pool = match condvar.wait_timeout(pool, std_timeout) {
Ok((mutex, _)) => mutex,
_ => return Err(Error::DriverError(DriverError::PoisonedPoolMutex)),
}
}
} else {
break;
}
}
let mut conn = pool.pool.pop().unwrap();
if !conn.ping() {
try!(conn.reset());
}
Ok(PooledConn {pool: self.clone(), conn: Some(conn)})
}
fn get_conn_by_stmt<T: AsRef<str>>(&self, query: T) -> MyResult<PooledConn> {
let conn = {
let &(ref inner_pool, _) = &*self.0;
let mut pool = match inner_pool.lock() {
Ok(mutex) => mutex,
_ => return Err(Error::DriverError(DriverError::PoisonedPoolMutex)),
};
let mut id = None;
for (i, conn) in pool.pool.iter().enumerate() {
if conn.has_stmt(query.as_ref()) {
id = Some(i);
break;
}
}
if let Some(id) = id {
let mut conn = pool.pool.remove(id);
if !conn.ping() {
try!(conn.reset());
}
Some(PooledConn {pool: self.clone(), conn: Some(conn)})
} else {
None
}
};
match conn {
Some(pooled_conn) => Ok(pooled_conn),
None => self.get_conn(),
}
}
pub fn prepare<'a, T: AsRef<str> + 'a>(&'a self, query: T) -> MyResult<Stmt<'a>> {
let conn = try!(self.get_conn_by_stmt(query.as_ref()));
conn.pooled_prepare(query)
}
pub fn prep_exec<'a, A: AsRef<str>, T: Into<Params>>(&'a self, query: A, params: T) -> MyResult<QueryResult<'a>> {
let conn = try!(self.get_conn_by_stmt(query.as_ref()));
conn.pooled_prep_exec(query, params)
}
pub fn start_transaction(&self,
consistent_snapshot: bool,
isolation_level: Option<IsolationLevel>,
readonly: Option<bool>) -> MyResult<Transaction> {
(try!(self.get_conn())).pooled_start_transaction(consistent_snapshot, isolation_level, readonly)
}
}
impl fmt::Debug for Pool {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let pool = (self.0).0.lock().unwrap();
write!(f, "Pool {{ min: {}, max: {} }}", pool.min, pool.max)
}
}
#[derive(Debug)]
pub struct PooledConn {
pool: Pool,
conn: Option<Conn>
}
impl Drop for PooledConn {
fn drop(&mut self) {
let mut pool = (self.pool.0).0.lock().unwrap();
if pool.count > pool.min || self.conn.is_none() {
pool.count -= 1;
} else {
pool.pool.push(self.conn.take().unwrap());
(self.pool.0).1.notify_one();
}
}
}
impl PooledConn {
pub fn query<'a, T: AsRef<str> + 'a>(&'a mut self, query: T) -> MyResult<QueryResult<'a>> {
self.conn.as_mut().unwrap().query(query)
}
pub fn prepare<'a, T: AsRef<str> + 'a>(&'a mut self, query: T) -> MyResult<Stmt<'a>> {
self.conn.as_mut().unwrap().prepare(query)
}
pub fn prep_exec<'a, A: AsRef<str> + 'a, T: Into<Params>>(&'a mut self, query: A, params: T) -> MyResult<QueryResult<'a>> {
self.conn.as_mut().unwrap().prep_exec(query, params)
}
pub fn start_transaction<'a>(&'a mut self,
consistent_snapshot: bool,
isolation_level: Option<IsolationLevel>,
readonly: Option<bool>) -> MyResult<Transaction<'a>> {
self.conn.as_mut().unwrap().start_transaction(consistent_snapshot,
isolation_level,
readonly)
}
pub fn as_mut<'a>(&'a mut self) -> &'a mut Conn {
self.conn.as_mut().unwrap()
}
pub fn as_ref<'a>(&'a self) -> &'a Conn {
self.conn.as_ref().unwrap()
}
pub fn unwrap(mut self) -> Conn {
self.conn.take().unwrap()
}
fn pooled_prepare<'a, T: AsRef<str>>(mut self, query: T) -> MyResult<Stmt<'a>> {
match self.as_mut()._prepare(query.as_ref()) {
Ok(stmt) => Ok(Stmt::new_pooled(stmt, self)),
Err(err) => Err(err)
}
}
fn pooled_prep_exec<'a, A, T>(mut self, query: A, params: T) -> MyResult<QueryResult<'a>>
where A: AsRef<str>,
T: Into<Params>
{
let stmt = try!(self.as_mut()._prepare(query.as_ref()));
let stmt = Stmt::new_pooled(stmt, self);
stmt.prep_exec(params)
}
fn pooled_start_transaction<'a>(mut self,
consistent_snapshot: bool,
isolation_level: Option<IsolationLevel>,
readonly: Option<bool>) -> MyResult<Transaction<'a>> {
let _ = try!(self.as_mut()._start_transaction(consistent_snapshot,
isolation_level,
readonly));
Ok(Transaction::new_pooled(self))
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod test {
use conn::Opts;
use std::default::Default;
pub static USER: &'static str = "root";
pub static PASS: &'static str = "password";
pub static ADDR: &'static str = "127.0.0.1";
pub static PORT: u16 = 3307;
#[cfg(feature = "openssl")]
pub fn get_opts() -> Opts {
let pwd: String = ::std::env::var("MYSQL_SERVER_PASS").unwrap_or(PASS.to_string());
let port: u16 = ::std::env::var("MYSQL_SERVER_PORT").ok()
.map(|my_port| my_port.parse().ok().unwrap_or(PORT))
.unwrap_or(PORT);
Opts {
user: Some(USER.to_string()),
pass: Some(pwd),
ip_or_hostname: Some(ADDR.to_string()),
tcp_port: port,
ssl_opts: Some((::std::convert::From::from("tests/ca-cert.pem"), None)),
..Default::default()
}
}
#[cfg(not(feature = "ssl"))]
pub fn get_opts() -> Opts {
let pwd: String = ::std::env::var("MYSQL_SERVER_PASS").unwrap_or(PASS.to_string());
let port: u16 = ::std::env::var("MYSQL_SERVER_PORT").ok()
.map(|my_port| my_port.parse().ok().unwrap_or(PORT))
.unwrap_or(PORT);
Opts {
user: Some(USER.to_string()),
pass: Some(pwd),
ip_or_hostname: Some(ADDR.to_string()),
tcp_port: port,
..Default::default()
}
}
mod pool {
use super::get_opts;
use std::thread;
use super::super::Pool;
use super::super::super::super::value::from_value;
use super::super::super::super::error::{Error, DriverError};
#[test]
fn should_execute_queryes_on_PooledConn() {
let pool = Pool::new(get_opts()).unwrap();
let mut threads = Vec::new();
for _ in 0usize..10 {
let pool = pool.clone();
threads.push(thread::spawn(move || {
let conn = pool.get_conn();
assert!(conn.is_ok());
let mut conn = conn.unwrap();
assert!(conn.query("SELECT 1").is_ok());
}));
}
for t in threads.into_iter() {
assert!(t.join().is_ok());
}
}
#[test]
fn should_timeout_if_no_connections_available() {
let pool = Pool::new_manual(0, 1, get_opts()).unwrap();
let conn1 = pool.try_get_conn(357).unwrap();
let conn2 = pool.try_get_conn(357);
assert!(conn2.is_err());
match conn2 {
Err(Error::DriverError(DriverError::Timeout)) => assert!(true),
_ => assert!(false),
}
drop(conn1);
assert!(pool.try_get_conn(357).is_ok());
}
#[test]
fn should_execute_statements_on_PooledConn() {
let pool = Pool::new(get_opts()).unwrap();
let mut threads = Vec::new();
for _ in 0usize..10 {
let pool = pool.clone();
threads.push(thread::spawn(move || {
let mut conn = pool.get_conn().unwrap();
let mut stmt = conn.prepare("SELECT 1").unwrap();
assert!(stmt.execute(()).is_ok());
}));
}
for t in threads.into_iter() {
assert!(t.join().is_ok());
}
let pool = Pool::new(get_opts()).unwrap();
let mut threads = Vec::new();
for _ in 0usize..10 {
let pool = pool.clone();
threads.push(thread::spawn(move || {
let mut conn = pool.get_conn().unwrap();
conn.prep_exec("SELECT ?", (1,)).unwrap();
}));
}
for t in threads.into_iter() {
assert!(t.join().is_ok());
}
}
#[test]
fn should_execute_statements_on_Pool() {
let pool = Pool::new(get_opts()).unwrap();
let mut threads = Vec::new();
for _ in 0usize..10 {
let pool = pool.clone();
threads.push(thread::spawn(move || {
let mut stmt = pool.prepare("SELECT 1").unwrap();
assert!(stmt.execute(()).is_ok());
}));
}
for t in threads.into_iter() {
assert!(t.join().is_ok());
}
let pool = Pool::new(get_opts()).unwrap();
let mut threads = Vec::new();
for _ in 0usize..10 {
let pool = pool.clone();
threads.push(thread::spawn(move || {
pool.prep_exec("SELECT ?", (1,)).unwrap();
}));
}
for t in threads.into_iter() {
assert!(t.join().is_ok());
}
}
#[test]
fn should_start_transaction_on_Pool() {
let pool = Pool::new(get_opts()).unwrap();
pool.prepare("CREATE TEMPORARY TABLE x.tbl(a INT)").ok().map(|mut stmt| {
assert!(stmt.execute(()).is_ok());
});
assert!(pool.start_transaction(false, None, None).and_then(|mut t| {
assert!(t.query("INSERT INTO x.tbl(a) VALUES(1)").is_ok());
assert!(t.query("INSERT INTO x.tbl(a) VALUES(2)").is_ok());
t.commit()
}).is_ok());
pool.prepare("SELECT COUNT(a) FROM x.tbl").ok().map(|mut stmt| {
for x in stmt.execute(()).unwrap() {
let mut x = x.unwrap();
assert_eq!(from_value::<u8>(x.take(0).unwrap()), 2u8);
}
});
assert!(pool.start_transaction(false, None, None).and_then(|mut t| {
assert!(t.query("INSERT INTO x.tbl(a) VALUES(1)").is_ok());
assert!(t.query("INSERT INTO x.tbl(a) VALUES(2)").is_ok());
t.rollback()
}).is_ok());
pool.prepare("SELECT COUNT(a) FROM x.tbl").ok().map(|mut stmt| {
for x in stmt.execute(()).unwrap() {
let mut x = x.unwrap();
assert_eq!(from_value::<u8>(x.take(0).unwrap()), 2u8);
}
});
assert!(pool.start_transaction(false, None, None).and_then(|mut t| {
assert!(t.query("INSERT INTO x.tbl(a) VALUES(1)").is_ok());
assert!(t.query("INSERT INTO x.tbl(a) VALUES(2)").is_ok());
Ok(())
}).is_ok());
pool.prepare("SELECT COUNT(a) FROM x.tbl").ok().map(|mut stmt| {
for x in stmt.execute(()).unwrap() {
let mut x = x.unwrap();
assert_eq!(from_value::<u8>(x.take(0).unwrap()), 2u8);
}
});
}
#[test]
fn should_start_transaction_on_PooledConn() {
let pool = Pool::new(get_opts()).unwrap();
let mut conn = pool.get_conn().unwrap();
assert!(conn.query("CREATE TEMPORARY TABLE x.tbl(a INT)").is_ok());
assert!(conn.start_transaction(false, None, None).and_then(|mut t| {
assert!(t.query("INSERT INTO x.tbl(a) VALUES(1)").is_ok());
assert!(t.query("INSERT INTO x.tbl(a) VALUES(2)").is_ok());
t.commit()
}).is_ok());
for x in conn.query("SELECT COUNT(a) FROM x.tbl").unwrap() {
let mut x = x.unwrap();
assert_eq!(from_value::<u8>(x.take(0).unwrap()), 2u8);
}
assert!(conn.start_transaction(false, None, None).and_then(|mut t| {
assert!(t.query("INSERT INTO x.tbl(a) VALUES(1)").is_ok());
assert!(t.query("INSERT INTO x.tbl(a) VALUES(2)").is_ok());
t.rollback()
}).is_ok());
for x in conn.query("SELECT COUNT(a) FROM x.tbl").unwrap() {
let mut x = x.unwrap();
assert_eq!(from_value::<u8>(x.take(0).unwrap()), 2u8);
}
assert!(conn.start_transaction(false, None, None).and_then(|mut t| {
assert!(t.query("INSERT INTO x.tbl(a) VALUES(1)").is_ok());
assert!(t.query("INSERT INTO x.tbl(a) VALUES(2)").is_ok());
Ok(())
}).is_ok());
for x in conn.query("SELECT COUNT(a) FROM x.tbl").unwrap() {
let mut x = x.unwrap();
assert_eq!(from_value::<u8>(x.take(0).unwrap()), 2u8);
}
}
}
}