use std::cell::Cell;
use std::fmt;
use std::ascii::AsciiExt;
use {bad_response, Result, Connection, TransactionInternals, ConfigInternals, IsolationLevelNew};
use error::Error;
use rows::Rows;
use stmt::Statement;
use types::ToSql;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsolationLevel {
ReadUncommitted,
ReadCommitted,
RepeatableRead,
Serializable,
}
impl IsolationLevelNew for IsolationLevel {
fn new(raw: &str) -> Result<IsolationLevel> {
if raw.eq_ignore_ascii_case("READ UNCOMMITTED") {
Ok(IsolationLevel::ReadUncommitted)
} else if raw.eq_ignore_ascii_case("READ COMMITTED") {
Ok(IsolationLevel::ReadCommitted)
} else if raw.eq_ignore_ascii_case("REPEATABLE READ") {
Ok(IsolationLevel::RepeatableRead)
} else if raw.eq_ignore_ascii_case("SERIALIZABLE") {
Ok(IsolationLevel::Serializable)
} else {
Err(Error::Io(bad_response()))
}
}
}
impl IsolationLevel {
fn to_sql(&self) -> &'static str {
match *self {
IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
IsolationLevel::ReadCommitted => "READ COMMITTED",
IsolationLevel::RepeatableRead => "REPEATABLE READ",
IsolationLevel::Serializable => "SERIALIZABLE",
}
}
}
#[derive(Debug)]
pub struct Config {
isolation_level: Option<IsolationLevel>,
read_only: Option<bool>,
deferrable: Option<bool>,
}
impl Default for Config {
fn default() -> Config {
Config {
isolation_level: None,
read_only: None,
deferrable: None,
}
}
}
impl ConfigInternals for Config {
fn build_command(&self, s: &mut String) {
let mut first = true;
if let Some(isolation_level) = self.isolation_level {
s.push_str(" ISOLATION LEVEL ");
s.push_str(isolation_level.to_sql());
first = false;
}
if let Some(read_only) = self.read_only {
if !first {
s.push(',');
}
if read_only {
s.push_str(" READ ONLY");
} else {
s.push_str(" READ WRITE");
}
first = false;
}
if let Some(deferrable) = self.deferrable {
if !first {
s.push(',');
}
if deferrable {
s.push_str(" DEFERRABLE");
} else {
s.push_str(" NOT DEFERRABLE");
}
}
}
}
impl Config {
pub fn new() -> Config {
Config::default()
}
pub fn isolation_level(&mut self, isolation_level: IsolationLevel) -> &mut Config {
self.isolation_level = Some(isolation_level);
self
}
pub fn read_only(&mut self, read_only: bool) -> &mut Config {
self.read_only = Some(read_only);
self
}
pub fn deferrable(&mut self, deferrable: bool) -> &mut Config {
self.deferrable = Some(deferrable);
self
}
}
pub struct Transaction<'conn> {
conn: &'conn Connection,
depth: u32,
savepoint_name: Option<String>,
commit: Cell<bool>,
finished: bool,
}
impl<'a> fmt::Debug for Transaction<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Transaction")
.field("commit", &self.commit.get())
.field("depth", &self.depth)
.finish()
}
}
impl<'conn> Drop for Transaction<'conn> {
fn drop(&mut self) {
if !self.finished {
let _ = self.finish_inner();
}
}
}
impl<'conn> TransactionInternals<'conn> for Transaction<'conn> {
fn new(conn: &'conn Connection, depth: u32) -> Transaction<'conn> {
Transaction {
conn: conn,
depth: depth,
savepoint_name: None,
commit: Cell::new(false),
finished: false,
}
}
fn conn(&self) -> &'conn Connection {
self.conn
}
fn depth(&self) -> u32 {
self.depth
}
}
impl<'conn> Transaction<'conn> {
fn finish_inner(&mut self) -> Result<()> {
let mut conn = self.conn.0.borrow_mut();
debug_assert!(self.depth == conn.trans_depth);
conn.trans_depth -= 1;
match (self.commit.get(), &self.savepoint_name) {
(false, &Some(ref sp)) => try!(conn.quick_query(&format!("ROLLBACK TO {}", sp))),
(false, &None) => try!(conn.quick_query("ROLLBACK")),
(true, &Some(ref sp)) => try!(conn.quick_query(&format!("RELEASE {}", sp))),
(true, &None) => try!(conn.quick_query("COMMIT")),
};
Ok(())
}
pub fn prepare(&self, query: &str) -> Result<Statement<'conn>> {
self.conn.prepare(query)
}
pub fn prepare_cached(&self, query: &str) -> Result<Statement<'conn>> {
self.conn.prepare_cached(query)
}
pub fn execute(&self, query: &str, params: &[&ToSql]) -> Result<u64> {
self.conn.execute(query, params)
}
pub fn query<'a>(&'a self, query: &str, params: &[&ToSql]) -> Result<Rows<'a>> {
self.conn.query(query, params)
}
pub fn batch_execute(&self, query: &str) -> Result<()> {
self.conn.batch_execute(query)
}
pub fn transaction<'a>(&'a self) -> Result<Transaction<'a>> {
self.savepoint("sp")
}
pub fn savepoint<'a>(&'a self, name: &str) -> Result<Transaction<'a>> {
let mut conn = self.conn.0.borrow_mut();
check_desync!(conn);
assert!(conn.trans_depth == self.depth,
"`savepoint` may only be called on the active transaction");
try!(conn.quick_query(&format!("SAVEPOINT {}", name)));
conn.trans_depth += 1;
Ok(Transaction {
conn: self.conn,
depth: self.depth + 1,
savepoint_name: Some(name.to_owned()),
commit: Cell::new(false),
finished: false,
})
}
pub fn connection(&self) -> &'conn Connection {
self.conn
}
pub fn is_active(&self) -> bool {
self.conn.0.borrow().trans_depth == self.depth
}
pub fn set_config(&self, config: &Config) -> Result<()> {
let mut command = "SET TRANSACTION".to_owned();
config.build_command(&mut command);
self.batch_execute(&command)
}
pub fn will_commit(&self) -> bool {
self.commit.get()
}
pub fn set_commit(&self) {
self.commit.set(true);
}
pub fn set_rollback(&self) {
self.commit.set(false);
}
pub fn commit(self) -> Result<()> {
self.set_commit();
self.finish()
}
pub fn finish(mut self) -> Result<()> {
self.finished = true;
self.finish_inner()
}
}