2013-09-03 02:08:37 +00:00
|
|
|
#[link(name = "postgres",
|
|
|
|
vers = "0.1",
|
|
|
|
url = "https://github.com/sfackler/rust-postgres")];
|
|
|
|
|
2013-08-22 05:52:15 +00:00
|
|
|
extern mod extra;
|
2013-08-04 02:17:32 +00:00
|
|
|
|
2013-09-04 05:42:24 +00:00
|
|
|
use extra::container::Deque;
|
2013-08-26 05:08:37 +00:00
|
|
|
use extra::digest::Digest;
|
2013-09-04 05:42:24 +00:00
|
|
|
use extra::ringbuf::RingBuf;
|
2013-08-26 05:08:37 +00:00
|
|
|
use extra::md5::Md5;
|
2013-08-27 04:19:24 +00:00
|
|
|
use extra::url::{UserInfo, Url};
|
2013-08-22 05:52:15 +00:00
|
|
|
use std::cell::Cell;
|
2013-08-28 06:23:36 +00:00
|
|
|
use std::hashmap::HashMap;
|
2013-09-01 00:12:19 +00:00
|
|
|
use std::rt::io::{io_error, Decorator};
|
|
|
|
use std::rt::io::mem::MemWriter;
|
2013-08-22 05:52:15 +00:00
|
|
|
use std::rt::io::net::ip::SocketAddr;
|
|
|
|
use std::rt::io::net::tcp::TcpStream;
|
2013-08-04 02:17:32 +00:00
|
|
|
|
2013-08-22 05:52:15 +00:00
|
|
|
use message::*;
|
2013-09-05 04:26:43 +00:00
|
|
|
use types::{PostgresType, ToSql, FromSql};
|
2013-07-25 07:10:18 +00:00
|
|
|
|
2013-08-22 05:52:15 +00:00
|
|
|
mod message;
|
2013-08-28 04:36:27 +00:00
|
|
|
mod types;
|
2013-07-25 07:10:18 +00:00
|
|
|
|
2013-08-28 06:23:36 +00:00
|
|
|
macro_rules! match_read_message(
|
|
|
|
($conn:expr, { $($($p:pat)|+ => $e:expr),+ }) => (
|
|
|
|
match {
|
|
|
|
let ref conn = $conn;
|
|
|
|
let resp;
|
|
|
|
loop {
|
|
|
|
match conn.read_message() {
|
2013-08-29 03:25:15 +00:00
|
|
|
NoticeResponse { fields } => handle_notice_response(fields),
|
2013-08-28 06:23:36 +00:00
|
|
|
msg => {
|
|
|
|
resp = msg;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resp
|
|
|
|
} {
|
|
|
|
$(
|
|
|
|
$($p)|+ => $e
|
|
|
|
),+
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2013-09-04 03:07:10 +00:00
|
|
|
macro_rules! match_read_message_or_fail(
|
|
|
|
($conn:expr, { $($($p:pat)|+ => $e:expr),+ }) => (
|
|
|
|
match_read_message!($conn, {
|
|
|
|
$(
|
|
|
|
$($p)|+ => $e
|
|
|
|
),+ ,
|
|
|
|
resp => fail2!("Bad response: {}", resp.to_str())
|
|
|
|
})
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2013-08-29 03:25:15 +00:00
|
|
|
fn handle_notice_response(fields: ~[(u8, ~str)]) {
|
2013-08-29 04:24:43 +00:00
|
|
|
let err = PostgresDbError::new(fields);
|
2013-09-04 03:07:10 +00:00
|
|
|
info2!("{}: {}", err.severity, err.message);
|
2013-08-28 06:23:36 +00:00
|
|
|
}
|
|
|
|
|
2013-08-27 04:19:24 +00:00
|
|
|
#[deriving(ToStr)]
|
|
|
|
pub enum PostgresConnectError {
|
|
|
|
InvalidUrl,
|
|
|
|
MissingUser,
|
2013-08-27 05:06:53 +00:00
|
|
|
DbError(PostgresDbError),
|
2013-08-27 04:19:24 +00:00
|
|
|
MissingPassword,
|
|
|
|
UnsupportedAuthentication
|
|
|
|
}
|
|
|
|
|
2013-08-27 05:06:53 +00:00
|
|
|
#[deriving(ToStr)]
|
2013-08-29 04:24:43 +00:00
|
|
|
pub enum PostgresErrorPosition {
|
|
|
|
Position(uint),
|
|
|
|
InternalPosition {
|
|
|
|
position: uint,
|
|
|
|
query: ~str
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[deriving(ToStr)]
|
|
|
|
pub struct PostgresDbError {
|
|
|
|
// This could almost be an enum, except the values can be localized :(
|
|
|
|
severity: ~str,
|
|
|
|
// Should probably end up as an enum
|
|
|
|
code: ~str,
|
|
|
|
message: ~str,
|
|
|
|
position: Option<PostgresErrorPosition>,
|
|
|
|
where: Option<~str>,
|
|
|
|
file: ~str,
|
|
|
|
line: uint,
|
|
|
|
routine: ~str
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PostgresDbError {
|
|
|
|
fn new(fields: ~[(u8, ~str)]) -> PostgresDbError {
|
|
|
|
// move_rev_iter is more efficient than move_iter
|
|
|
|
let mut map: HashMap<u8, ~str> = fields.move_rev_iter().collect();
|
|
|
|
PostgresDbError {
|
|
|
|
severity: map.pop(&('S' as u8)).unwrap(),
|
|
|
|
code: map.pop(&('C' as u8)).unwrap(),
|
|
|
|
message: map.pop(&('M' as u8)).unwrap(),
|
|
|
|
position: match map.pop(&('P' as u8)) {
|
|
|
|
Some(pos) => Some(Position(FromStr::from_str(pos).unwrap())),
|
|
|
|
None => match map.pop(&('p' as u8)) {
|
|
|
|
Some(pos) => Some(InternalPosition {
|
|
|
|
position: FromStr::from_str(pos).unwrap(),
|
|
|
|
query: map.pop(&('q' as u8)).unwrap()
|
|
|
|
}),
|
|
|
|
None => None
|
|
|
|
}
|
|
|
|
},
|
|
|
|
where: map.pop(&('W' as u8)),
|
|
|
|
file: map.pop(&('F' as u8)).unwrap(),
|
|
|
|
line: FromStr::from_str(map.pop(&('L' as u8)).unwrap()).unwrap(),
|
|
|
|
routine: map.pop(&('R' as u8)).unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-08-27 05:06:53 +00:00
|
|
|
|
2013-08-29 06:19:53 +00:00
|
|
|
pub struct PostgresConnection {
|
|
|
|
priv stream: Cell<TcpStream>,
|
|
|
|
priv next_stmt_id: Cell<int>
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for PostgresConnection {
|
|
|
|
fn drop(&self) {
|
|
|
|
do io_error::cond.trap(|_| {}).inside {
|
|
|
|
self.write_message(&Terminate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-18 03:30:31 +00:00
|
|
|
impl PostgresConnection {
|
2013-08-23 02:47:06 +00:00
|
|
|
pub fn connect(url: &str) -> PostgresConnection {
|
2013-08-27 04:19:24 +00:00
|
|
|
match PostgresConnection::try_connect(url) {
|
|
|
|
Ok(conn) => conn,
|
2013-09-04 03:07:10 +00:00
|
|
|
Err(err) => fail2!("Failed to connect: {}", err.to_str())
|
2013-08-27 04:19:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_connect(url: &str) -> Result<PostgresConnection,
|
|
|
|
PostgresConnectError> {
|
|
|
|
let Url {
|
|
|
|
host,
|
|
|
|
port,
|
|
|
|
user,
|
|
|
|
path,
|
|
|
|
query: args,
|
|
|
|
_
|
|
|
|
}: Url = match FromStr::from_str(url) {
|
|
|
|
Some(url) => url,
|
|
|
|
None => return Err(InvalidUrl)
|
|
|
|
};
|
|
|
|
let user = match user {
|
|
|
|
Some(user) => user,
|
|
|
|
None => return Err(MissingUser)
|
|
|
|
};
|
|
|
|
let mut args = args;
|
|
|
|
|
2013-08-27 05:06:53 +00:00
|
|
|
// This seems silly
|
2013-09-02 05:29:13 +00:00
|
|
|
let socket_url = format!("{}:{}", host,
|
2013-08-27 05:06:53 +00:00
|
|
|
port.unwrap_or_default(~"5432"));
|
2013-08-27 04:19:24 +00:00
|
|
|
let addr: SocketAddr = match FromStr::from_str(socket_url) {
|
|
|
|
Some(addr) => addr,
|
|
|
|
None => return Err(InvalidUrl)
|
|
|
|
};
|
2013-08-22 05:52:15 +00:00
|
|
|
|
|
|
|
let conn = PostgresConnection {
|
2013-08-27 04:19:24 +00:00
|
|
|
// Need to figure out what to do about unwrap here
|
2013-08-22 05:52:15 +00:00
|
|
|
stream: Cell::new(TcpStream::connect(addr).unwrap()),
|
2013-08-18 03:42:40 +00:00
|
|
|
next_stmt_id: Cell::new(0)
|
|
|
|
};
|
|
|
|
|
2013-08-27 05:06:53 +00:00
|
|
|
// We have to clone here since we need the user again for auth
|
2013-08-27 04:19:24 +00:00
|
|
|
args.push((~"user", user.user.clone()));
|
|
|
|
if !path.is_empty() {
|
|
|
|
args.push((~"database", path));
|
2013-08-04 02:17:32 +00:00
|
|
|
}
|
2013-08-29 03:25:15 +00:00
|
|
|
conn.write_message(&StartupMessage {
|
|
|
|
version: PROTOCOL_VERSION,
|
|
|
|
parameters: args.as_slice()
|
|
|
|
});
|
2013-08-26 05:08:37 +00:00
|
|
|
|
2013-08-27 04:19:24 +00:00
|
|
|
match conn.handle_auth(user) {
|
|
|
|
Some(err) => return Err(err),
|
|
|
|
None => ()
|
|
|
|
}
|
2013-08-04 02:17:32 +00:00
|
|
|
|
2013-08-22 05:52:15 +00:00
|
|
|
loop {
|
2013-09-04 03:07:10 +00:00
|
|
|
match_read_message_or_fail!(conn, {
|
2013-08-29 03:25:15 +00:00
|
|
|
ParameterStatus { parameter, value } =>
|
|
|
|
info!("Parameter %s = %s", parameter, value),
|
2013-08-29 04:24:43 +00:00
|
|
|
BackendKeyData {_} => (),
|
2013-09-04 03:07:10 +00:00
|
|
|
ReadyForQuery {_} => break
|
2013-08-28 06:23:36 +00:00
|
|
|
})
|
2013-08-05 00:48:48 +00:00
|
|
|
}
|
2013-08-23 05:24:14 +00:00
|
|
|
|
2013-08-27 04:19:24 +00:00
|
|
|
Ok(conn)
|
2013-08-05 00:48:48 +00:00
|
|
|
}
|
|
|
|
|
2013-09-01 00:12:19 +00:00
|
|
|
fn write_messages(&self, messages: &[&FrontendMessage]) {
|
|
|
|
let mut buf = MemWriter::new();
|
|
|
|
for &message in messages.iter() {
|
|
|
|
buf.write_message(message);
|
|
|
|
}
|
|
|
|
do self.stream.with_mut_ref |s| {
|
|
|
|
s.write(buf.inner_ref().as_slice());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-22 06:41:26 +00:00
|
|
|
fn write_message(&self, message: &FrontendMessage) {
|
2013-08-22 05:52:15 +00:00
|
|
|
do self.stream.with_mut_ref |s| {
|
2013-08-22 06:41:26 +00:00
|
|
|
s.write_message(message);
|
2013-07-25 07:10:18 +00:00
|
|
|
}
|
2013-08-22 06:41:26 +00:00
|
|
|
}
|
2013-08-04 02:17:32 +00:00
|
|
|
|
2013-08-22 06:41:26 +00:00
|
|
|
fn read_message(&self) -> BackendMessage {
|
2013-08-22 05:52:15 +00:00
|
|
|
do self.stream.with_mut_ref |s| {
|
2013-08-22 06:41:26 +00:00
|
|
|
s.read_message()
|
2013-08-04 05:21:16 +00:00
|
|
|
}
|
2013-08-22 06:41:26 +00:00
|
|
|
}
|
|
|
|
|
2013-08-27 04:19:24 +00:00
|
|
|
fn handle_auth(&self, user: UserInfo) -> Option<PostgresConnectError> {
|
2013-09-04 03:07:10 +00:00
|
|
|
match_read_message_or_fail!(self, {
|
2013-08-27 04:19:24 +00:00
|
|
|
AuthenticationOk => return None,
|
|
|
|
AuthenticationCleartextPassword => {
|
|
|
|
let pass = match user.pass {
|
|
|
|
Some(pass) => pass,
|
|
|
|
None => return Some(MissingPassword)
|
|
|
|
};
|
2013-08-29 03:25:15 +00:00
|
|
|
self.write_message(&PasswordMessage { password: pass });
|
2013-08-28 06:23:36 +00:00
|
|
|
},
|
2013-08-29 03:25:15 +00:00
|
|
|
AuthenticationMD5Password { salt } => {
|
2013-08-27 04:19:24 +00:00
|
|
|
let UserInfo { user, pass } = user;
|
|
|
|
let pass = match pass {
|
|
|
|
Some(pass) => pass,
|
|
|
|
None => return Some(MissingPassword)
|
|
|
|
};
|
|
|
|
let input = pass + user;
|
|
|
|
let mut md5 = Md5::new();
|
|
|
|
md5.input_str(input);
|
|
|
|
let output = md5.result_str();
|
|
|
|
md5.reset();
|
|
|
|
md5.input_str(output);
|
2013-08-27 05:06:53 +00:00
|
|
|
md5.input(salt);
|
2013-08-27 04:19:24 +00:00
|
|
|
let output = "md5" + md5.result_str();
|
2013-08-29 03:25:15 +00:00
|
|
|
self.write_message(&PasswordMessage {
|
|
|
|
password: output.as_slice()
|
|
|
|
});
|
2013-09-04 03:07:10 +00:00
|
|
|
}
|
2013-08-28 06:23:36 +00:00
|
|
|
})
|
2013-08-27 04:19:24 +00:00
|
|
|
|
2013-09-04 03:07:10 +00:00
|
|
|
match_read_message_or_fail!(self, {
|
2013-08-27 04:19:24 +00:00
|
|
|
AuthenticationOk => None,
|
2013-08-29 04:24:43 +00:00
|
|
|
ErrorResponse { fields } =>
|
2013-09-04 03:07:10 +00:00
|
|
|
Some(DbError(PostgresDbError::new(fields)))
|
2013-08-28 06:23:36 +00:00
|
|
|
})
|
2013-08-26 05:08:37 +00:00
|
|
|
}
|
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
pub fn prepare<'a>(&'a self, query: &str) -> NormalPostgresStatement<'a> {
|
2013-08-27 05:06:53 +00:00
|
|
|
match self.try_prepare(query) {
|
|
|
|
Ok(stmt) => stmt,
|
2013-09-04 03:07:10 +00:00
|
|
|
Err(err) => fail2!("Error preparing \"{}\": {}", query,
|
|
|
|
err.to_str())
|
2013-08-27 05:06:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_prepare<'a>(&'a self, query: &str)
|
2013-09-04 05:05:04 +00:00
|
|
|
-> Result<NormalPostgresStatement<'a>, PostgresDbError> {
|
2013-08-22 06:41:26 +00:00
|
|
|
let id = self.next_stmt_id.take();
|
2013-08-27 02:38:02 +00:00
|
|
|
let stmt_name = format!("statement_{}", id);
|
2013-08-22 06:41:26 +00:00
|
|
|
self.next_stmt_id.put_back(id + 1);
|
2013-08-05 00:48:48 +00:00
|
|
|
|
2013-08-22 06:41:26 +00:00
|
|
|
let types = [];
|
2013-09-01 00:12:19 +00:00
|
|
|
self.write_messages([
|
2013-09-02 17:27:09 +00:00
|
|
|
&Parse {
|
|
|
|
name: stmt_name,
|
|
|
|
query: query,
|
|
|
|
param_types: types
|
|
|
|
},
|
|
|
|
&Describe {
|
|
|
|
variant: 'S' as u8,
|
|
|
|
name: stmt_name
|
|
|
|
},
|
|
|
|
&Sync]);
|
2013-08-22 06:41:26 +00:00
|
|
|
|
2013-09-04 03:07:10 +00:00
|
|
|
match_read_message_or_fail!(self, {
|
2013-08-22 05:52:15 +00:00
|
|
|
ParseComplete => (),
|
2013-08-29 05:33:27 +00:00
|
|
|
ErrorResponse { fields } => {
|
|
|
|
self.wait_for_ready();
|
|
|
|
return Err(PostgresDbError::new(fields));
|
2013-09-04 03:07:10 +00:00
|
|
|
}
|
2013-08-29 05:33:27 +00:00
|
|
|
})
|
2013-08-22 06:41:26 +00:00
|
|
|
|
2013-09-04 03:07:10 +00:00
|
|
|
let param_types = match_read_message_or_fail!(self, {
|
2013-09-05 04:26:43 +00:00
|
|
|
ParameterDescription { types } =>
|
|
|
|
types.iter().map(|ty| { PostgresType::from_oid(*ty) })
|
|
|
|
.collect()
|
2013-08-30 05:58:26 +00:00
|
|
|
});
|
2013-08-22 06:41:26 +00:00
|
|
|
|
2013-09-04 03:07:10 +00:00
|
|
|
let result_desc = match_read_message_or_fail!(self, {
|
2013-09-05 04:26:43 +00:00
|
|
|
RowDescription { descriptions } => {
|
|
|
|
let mut res: ~[ResultDescription] = descriptions
|
|
|
|
.move_rev_iter().map(|desc| {
|
|
|
|
ResultDescription::from_row_description_entry(desc)
|
|
|
|
}).collect();
|
|
|
|
res.reverse();
|
|
|
|
res
|
|
|
|
},
|
2013-09-04 03:07:10 +00:00
|
|
|
NoData => ~[]
|
2013-09-01 21:09:08 +00:00
|
|
|
});
|
2013-08-17 22:09:26 +00:00
|
|
|
|
2013-08-22 06:41:26 +00:00
|
|
|
self.wait_for_ready();
|
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
Ok(NormalPostgresStatement {
|
2013-08-22 06:41:26 +00:00
|
|
|
conn: self,
|
|
|
|
name: stmt_name,
|
2013-08-30 05:58:26 +00:00
|
|
|
param_types: param_types,
|
2013-09-01 21:09:08 +00:00
|
|
|
result_desc: result_desc,
|
2013-09-04 05:42:24 +00:00
|
|
|
next_portal_id: Cell::new(0)
|
2013-08-27 05:06:53 +00:00
|
|
|
})
|
2013-08-22 06:41:26 +00:00
|
|
|
}
|
|
|
|
|
2013-09-05 06:40:22 +00:00
|
|
|
|
2013-08-29 05:44:34 +00:00
|
|
|
pub fn in_transaction<T>(&self, blk: &fn(&PostgresTransaction) -> T) -> T {
|
2013-08-23 07:13:42 +00:00
|
|
|
self.quick_query("BEGIN");
|
2013-08-23 05:24:14 +00:00
|
|
|
|
2013-08-27 05:40:23 +00:00
|
|
|
let trans = PostgresTransaction {
|
|
|
|
conn: self,
|
|
|
|
commit: Cell::new(true)
|
|
|
|
};
|
2013-08-23 05:24:14 +00:00
|
|
|
// If this fails, Postgres will rollback when the connection closes
|
2013-08-27 05:40:23 +00:00
|
|
|
let ret = blk(&trans);
|
2013-08-23 05:24:14 +00:00
|
|
|
|
2013-08-27 05:40:23 +00:00
|
|
|
if trans.commit.take() {
|
2013-08-23 07:13:42 +00:00
|
|
|
self.quick_query("COMMIT");
|
2013-08-23 05:24:14 +00:00
|
|
|
} else {
|
2013-08-23 07:13:42 +00:00
|
|
|
self.quick_query("ROLLBACK");
|
2013-08-23 05:24:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
2013-09-01 18:06:33 +00:00
|
|
|
pub fn update(&self, query: &str, params: &[&ToSql]) -> uint {
|
|
|
|
match self.try_update(query, params) {
|
|
|
|
Ok(res) => res,
|
2013-09-04 03:07:10 +00:00
|
|
|
Err(err) => fail2!("Error running update: {}", err.to_str())
|
2013-09-01 18:06:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_update(&self, query: &str, params: &[&ToSql])
|
|
|
|
-> Result<uint, PostgresDbError> {
|
|
|
|
do self.try_prepare(query).chain |stmt| {
|
|
|
|
stmt.try_update(params)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-23 07:13:42 +00:00
|
|
|
fn quick_query(&self, query: &str) {
|
2013-08-29 03:25:15 +00:00
|
|
|
self.write_message(&Query { query: query });
|
2013-08-23 07:13:42 +00:00
|
|
|
|
|
|
|
loop {
|
2013-08-28 06:23:36 +00:00
|
|
|
match_read_message!(self, {
|
2013-08-29 03:25:15 +00:00
|
|
|
ReadyForQuery {_} => break,
|
2013-08-29 04:24:43 +00:00
|
|
|
ErrorResponse { fields } =>
|
2013-09-04 03:07:10 +00:00
|
|
|
fail2!("Error: {}", PostgresDbError::new(fields).to_str()),
|
2013-08-23 07:13:42 +00:00
|
|
|
_ => ()
|
2013-08-28 06:23:36 +00:00
|
|
|
})
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-22 06:41:26 +00:00
|
|
|
fn wait_for_ready(&self) {
|
2013-09-04 03:07:10 +00:00
|
|
|
match_read_message_or_fail!(self, {
|
|
|
|
ReadyForQuery {_} => ()
|
2013-09-01 21:09:08 +00:00
|
|
|
})
|
2013-08-17 22:09:26 +00:00
|
|
|
}
|
|
|
|
}
|
2013-08-18 02:09:56 +00:00
|
|
|
|
2013-08-27 05:40:23 +00:00
|
|
|
pub struct PostgresTransaction<'self> {
|
|
|
|
priv conn: &'self PostgresConnection,
|
|
|
|
priv commit: Cell<bool>
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'self> PostgresTransaction<'self> {
|
2013-09-04 05:05:04 +00:00
|
|
|
pub fn prepare<'a>(&'a self, query: &str)
|
|
|
|
-> TransactionalPostgresStatement<'a> {
|
|
|
|
TransactionalPostgresStatement { stmt: self.conn.prepare(query) }
|
2013-08-27 05:40:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_prepare<'a>(&'a self, query: &str)
|
2013-09-04 05:05:04 +00:00
|
|
|
-> Result<TransactionalPostgresStatement<'a>, PostgresDbError> {
|
|
|
|
do self.conn.try_prepare(query).map_move |stmt| {
|
|
|
|
TransactionalPostgresStatement { stmt: stmt }
|
|
|
|
}
|
2013-08-27 05:40:23 +00:00
|
|
|
}
|
|
|
|
|
2013-09-01 18:06:33 +00:00
|
|
|
pub fn update(&self, query: &str, params: &[&ToSql]) -> uint {
|
|
|
|
self.conn.update(query, params)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_update(&self, query: &str, params: &[&ToSql])
|
|
|
|
-> Result<uint, PostgresDbError> {
|
|
|
|
self.conn.try_update(query, params)
|
|
|
|
}
|
|
|
|
|
2013-09-05 06:28:44 +00:00
|
|
|
pub fn in_transaction<T>(&self, blk: &fn(&PostgresTransaction) -> T) -> T {
|
2013-09-05 06:40:22 +00:00
|
|
|
self.conn.quick_query("SAVEPOINT sp");
|
2013-09-05 06:28:44 +00:00
|
|
|
|
|
|
|
let nested_trans = PostgresTransaction {
|
|
|
|
conn: self.conn,
|
|
|
|
commit: Cell::new(true)
|
|
|
|
};
|
|
|
|
|
|
|
|
let ret = blk(&nested_trans);
|
|
|
|
|
|
|
|
if nested_trans.commit.take() {
|
2013-09-05 06:40:22 +00:00
|
|
|
self.conn.quick_query("RELEASE sp");
|
2013-09-05 06:28:44 +00:00
|
|
|
} else {
|
2013-09-05 06:40:22 +00:00
|
|
|
self.conn.quick_query("ROLLBACK TO sp");
|
2013-09-05 06:28:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
2013-08-27 05:40:23 +00:00
|
|
|
pub fn will_commit(&self) -> bool {
|
|
|
|
let commit = self.commit.take();
|
|
|
|
self.commit.put_back(commit);
|
|
|
|
commit
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_commit(&self) {
|
|
|
|
self.commit.take();
|
|
|
|
self.commit.put_back(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_rollback(&self) {
|
|
|
|
self.commit.take();
|
|
|
|
self.commit.put_back(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
pub trait PostgresStatement {
|
2013-09-05 04:51:21 +00:00
|
|
|
fn param_types<'a>(&'a self) -> &'a [PostgresType];
|
|
|
|
fn result_descriptions<'a>(&'a self) -> &'a [ResultDescription];
|
2013-09-04 05:05:04 +00:00
|
|
|
fn update(&self, params: &[&ToSql]) -> uint;
|
|
|
|
fn try_update(&self, params: &[&ToSql]) -> Result<uint, PostgresDbError>;
|
|
|
|
fn query<'a>(&'a self, params: &[&ToSql]) -> PostgresResult<'a>;
|
|
|
|
fn try_query<'a>(&'a self, params: &[&ToSql])
|
|
|
|
-> Result<PostgresResult<'a>, PostgresDbError>;
|
|
|
|
fn find_col_named(&self, col: &str) -> Option<uint>;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct NormalPostgresStatement<'self> {
|
2013-08-22 05:52:15 +00:00
|
|
|
priv conn: &'self PostgresConnection,
|
2013-08-22 06:41:26 +00:00
|
|
|
priv name: ~str,
|
2013-09-05 04:26:43 +00:00
|
|
|
priv param_types: ~[PostgresType],
|
|
|
|
priv result_desc: ~[ResultDescription],
|
2013-09-04 05:42:24 +00:00
|
|
|
priv next_portal_id: Cell<uint>
|
2013-08-22 07:12:35 +00:00
|
|
|
}
|
|
|
|
|
2013-09-05 04:51:21 +00:00
|
|
|
#[deriving(Eq)]
|
2013-09-05 04:26:43 +00:00
|
|
|
pub struct ResultDescription {
|
|
|
|
name: ~str,
|
|
|
|
ty: PostgresType
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ResultDescription {
|
|
|
|
fn from_row_description_entry(row: RowDescriptionEntry)
|
|
|
|
-> ResultDescription {
|
|
|
|
let RowDescriptionEntry { name, type_oid, _ } = row;
|
|
|
|
|
|
|
|
ResultDescription {
|
|
|
|
name: name,
|
|
|
|
ty: PostgresType::from_oid(type_oid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-22 07:12:35 +00:00
|
|
|
#[unsafe_destructor]
|
2013-09-04 05:05:04 +00:00
|
|
|
impl<'self> Drop for NormalPostgresStatement<'self> {
|
2013-08-22 07:12:35 +00:00
|
|
|
fn drop(&self) {
|
2013-08-26 07:36:09 +00:00
|
|
|
do io_error::cond.trap(|_| {}).inside {
|
2013-09-01 00:12:19 +00:00
|
|
|
self.conn.write_messages([
|
2013-09-01 05:48:49 +00:00
|
|
|
&Close {
|
|
|
|
variant: 'S' as u8,
|
|
|
|
name: self.name.as_slice()
|
|
|
|
},
|
|
|
|
&Sync]);
|
2013-08-26 07:36:09 +00:00
|
|
|
loop {
|
2013-08-28 06:23:36 +00:00
|
|
|
match_read_message!(self.conn, {
|
2013-08-29 03:25:15 +00:00
|
|
|
ReadyForQuery {_} => break,
|
2013-08-26 07:36:09 +00:00
|
|
|
_ => ()
|
2013-08-28 06:23:36 +00:00
|
|
|
})
|
2013-08-23 05:24:14 +00:00
|
|
|
}
|
|
|
|
}
|
2013-08-22 07:12:35 +00:00
|
|
|
}
|
2013-08-18 02:09:56 +00:00
|
|
|
}
|
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
impl<'self> NormalPostgresStatement<'self> {
|
2013-09-04 05:42:24 +00:00
|
|
|
fn execute(&self, portal_name: &str, row_limit: uint, params: &[&ToSql])
|
2013-09-01 05:48:49 +00:00
|
|
|
-> Option<PostgresDbError> {
|
2013-08-30 05:58:26 +00:00
|
|
|
let mut formats = ~[];
|
|
|
|
let mut values = ~[];
|
|
|
|
for (¶m, &ty) in params.iter().zip(self.param_types.iter()) {
|
|
|
|
let (format, value) = param.to_sql(ty);
|
|
|
|
formats.push(format as i16);
|
|
|
|
values.push(value);
|
|
|
|
};
|
|
|
|
|
2013-09-02 19:42:24 +00:00
|
|
|
let result_formats: ~[i16] = self.result_desc.iter().map(|desc| {
|
2013-09-05 04:26:43 +00:00
|
|
|
desc.ty.result_format() as i16
|
2013-09-02 19:42:24 +00:00
|
|
|
}).collect();
|
2013-08-22 07:12:35 +00:00
|
|
|
|
2013-09-01 00:12:19 +00:00
|
|
|
self.conn.write_messages([
|
2013-09-01 05:48:49 +00:00
|
|
|
&Bind {
|
|
|
|
portal: portal_name,
|
|
|
|
statement: self.name.as_slice(),
|
|
|
|
formats: formats,
|
|
|
|
values: values,
|
|
|
|
result_formats: result_formats
|
|
|
|
},
|
2013-09-03 06:09:30 +00:00
|
|
|
&Execute {
|
|
|
|
portal: portal_name,
|
2013-09-04 05:42:24 +00:00
|
|
|
max_rows: row_limit as i32
|
2013-09-03 06:09:30 +00:00
|
|
|
},
|
2013-09-01 05:48:49 +00:00
|
|
|
&Sync]);
|
2013-08-22 07:12:35 +00:00
|
|
|
|
2013-09-04 03:07:10 +00:00
|
|
|
match_read_message_or_fail!(self.conn, {
|
2013-08-27 05:06:53 +00:00
|
|
|
BindComplete => None,
|
2013-09-03 06:09:30 +00:00
|
|
|
ErrorResponse { fields } => {
|
|
|
|
self.conn.wait_for_ready();
|
|
|
|
Some(PostgresDbError::new(fields))
|
2013-09-04 03:07:10 +00:00
|
|
|
}
|
2013-09-03 06:09:30 +00:00
|
|
|
})
|
2013-08-23 05:24:14 +00:00
|
|
|
}
|
2013-09-04 05:20:21 +00:00
|
|
|
|
|
|
|
fn lazy_query<'a>(&'a self, row_limit: uint, params: &[&ToSql])
|
|
|
|
-> PostgresResult<'a> {
|
|
|
|
match self.try_lazy_query(row_limit, params) {
|
|
|
|
Ok(result) => result,
|
|
|
|
Err(err) => fail2!("Error executing query: {}", err.to_str())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-04 05:42:24 +00:00
|
|
|
fn try_lazy_query<'a>(&'a self, row_limit: uint, params: &[&ToSql])
|
2013-09-04 05:20:21 +00:00
|
|
|
-> Result<PostgresResult<'a>, PostgresDbError> {
|
2013-09-04 05:42:24 +00:00
|
|
|
let id = self.next_portal_id.take();
|
|
|
|
let portal_name = format!("{}_portal_{}", self.name, id);
|
|
|
|
self.next_portal_id.put_back(id + 1);
|
|
|
|
|
|
|
|
match self.execute(portal_name, row_limit, params) {
|
2013-09-04 05:20:21 +00:00
|
|
|
Some(err) => {
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
None => ()
|
|
|
|
}
|
|
|
|
|
2013-09-04 05:42:24 +00:00
|
|
|
let mut result = PostgresResult {
|
2013-09-04 05:20:21 +00:00
|
|
|
stmt: self,
|
2013-09-04 05:42:24 +00:00
|
|
|
name: portal_name,
|
|
|
|
data: RingBuf::new(),
|
|
|
|
row_limit: row_limit,
|
|
|
|
more_rows: true
|
|
|
|
};
|
|
|
|
result.load_rows();
|
|
|
|
|
|
|
|
Ok(result)
|
2013-09-04 05:20:21 +00:00
|
|
|
}
|
2013-09-04 05:05:04 +00:00
|
|
|
}
|
2013-08-23 05:24:14 +00:00
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
impl<'self> PostgresStatement for NormalPostgresStatement<'self> {
|
2013-09-05 04:51:21 +00:00
|
|
|
fn param_types<'a>(&'a self) -> &'a [PostgresType] {
|
|
|
|
self.param_types.as_slice()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn result_descriptions<'a>(&'a self) -> &'a [ResultDescription] {
|
|
|
|
self.result_desc.as_slice()
|
2013-09-04 05:05:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn update(&self, params: &[&ToSql]) -> uint {
|
2013-08-27 05:06:53 +00:00
|
|
|
match self.try_update(params) {
|
|
|
|
Ok(count) => count,
|
2013-09-04 03:07:10 +00:00
|
|
|
Err(err) => fail2!("Error running update: {}", err.to_str())
|
2013-08-27 05:06:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
fn try_update(&self, params: &[&ToSql])
|
2013-08-27 05:06:53 +00:00
|
|
|
-> Result<uint, PostgresDbError> {
|
2013-09-04 05:42:24 +00:00
|
|
|
match self.execute("", 0, params) {
|
2013-08-27 05:06:53 +00:00
|
|
|
Some(err) => {
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
None => ()
|
|
|
|
}
|
2013-08-22 07:12:35 +00:00
|
|
|
|
2013-08-29 05:44:34 +00:00
|
|
|
let num;
|
2013-08-23 05:24:14 +00:00
|
|
|
loop {
|
2013-09-04 03:07:10 +00:00
|
|
|
match_read_message_or_fail!(self.conn, {
|
2013-08-29 03:25:15 +00:00
|
|
|
CommandComplete { tag } => {
|
|
|
|
let s = tag.split_iter(' ').last().unwrap();
|
2013-08-29 05:44:34 +00:00
|
|
|
num = match FromStr::from_str(s) {
|
|
|
|
None => 0,
|
|
|
|
Some(n) => n
|
|
|
|
};
|
2013-08-23 05:24:14 +00:00
|
|
|
break;
|
2013-08-28 06:23:36 +00:00
|
|
|
},
|
2013-08-29 03:25:15 +00:00
|
|
|
DataRow {_} => (),
|
2013-08-29 05:44:34 +00:00
|
|
|
EmptyQueryResponse => {
|
|
|
|
num = 0;
|
|
|
|
break;
|
|
|
|
},
|
2013-08-29 03:25:15 +00:00
|
|
|
NoticeResponse {_} => (),
|
2013-08-29 04:24:43 +00:00
|
|
|
ErrorResponse { fields } => {
|
2013-08-27 05:06:53 +00:00
|
|
|
self.conn.wait_for_ready();
|
2013-08-29 04:24:43 +00:00
|
|
|
return Err(PostgresDbError::new(fields));
|
2013-09-04 03:07:10 +00:00
|
|
|
}
|
2013-08-28 06:23:36 +00:00
|
|
|
})
|
2013-08-23 05:24:14 +00:00
|
|
|
}
|
2013-08-22 07:12:35 +00:00
|
|
|
self.conn.wait_for_ready();
|
2013-08-23 05:24:14 +00:00
|
|
|
|
2013-08-27 05:06:53 +00:00
|
|
|
Ok(num)
|
2013-08-22 07:12:35 +00:00
|
|
|
}
|
2013-08-23 07:13:42 +00:00
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
fn query<'a>(&'a self, params: &[&ToSql])
|
|
|
|
-> PostgresResult<'a> {
|
2013-09-04 05:20:21 +00:00
|
|
|
self.lazy_query(0, params)
|
2013-08-27 05:06:53 +00:00
|
|
|
}
|
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
fn try_query<'a>(&'a self, params: &[&ToSql])
|
|
|
|
-> Result<PostgresResult<'a>, PostgresDbError> {
|
2013-09-04 05:20:21 +00:00
|
|
|
self.try_lazy_query(0, params)
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
2013-09-03 00:07:08 +00:00
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
fn find_col_named(&self, col: &str) -> Option<uint> {
|
2013-09-03 00:07:08 +00:00
|
|
|
do self.result_desc.iter().position |desc| {
|
|
|
|
desc.name.as_slice() == col
|
|
|
|
}
|
|
|
|
}
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
|
|
|
|
2013-09-04 05:05:04 +00:00
|
|
|
pub struct TransactionalPostgresStatement<'self> {
|
|
|
|
priv stmt: NormalPostgresStatement<'self>
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'self> PostgresStatement for TransactionalPostgresStatement<'self> {
|
2013-09-05 04:51:21 +00:00
|
|
|
fn param_types<'a>(&'a self) -> &'a [PostgresType] {
|
|
|
|
self.stmt.param_types()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn result_descriptions<'a>(&'a self) -> &'a [ResultDescription] {
|
|
|
|
self.stmt.result_descriptions()
|
2013-09-04 05:05:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn update(&self, params: &[&ToSql]) -> uint {
|
|
|
|
self.stmt.update(params)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn try_update(&self, params: &[&ToSql]) -> Result<uint, PostgresDbError> {
|
|
|
|
self.stmt.try_update(params)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn query<'a>(&'a self, params: &[&ToSql]) -> PostgresResult<'a> {
|
|
|
|
self.stmt.query(params)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn try_query<'a>(&'a self, params: &[&ToSql])
|
|
|
|
-> Result<PostgresResult<'a>, PostgresDbError> {
|
|
|
|
self.stmt.try_query(params)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_col_named(&self, col: &str) -> Option<uint> {
|
|
|
|
self.stmt.find_col_named(col)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-04 05:20:21 +00:00
|
|
|
impl<'self> TransactionalPostgresStatement<'self> {
|
|
|
|
pub fn lazy_query<'a>(&'a self, row_limit: uint, params: &[&ToSql])
|
|
|
|
-> PostgresResult<'a> {
|
|
|
|
self.stmt.lazy_query(row_limit, params)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_lazy_query<'a>(&'a self, row_limit: uint, params: &[&ToSql])
|
|
|
|
-> Result<PostgresResult<'a>, PostgresDbError> {
|
2013-09-05 06:48:02 +00:00
|
|
|
self.stmt.try_lazy_query(row_limit, params)
|
2013-09-04 05:20:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-23 07:13:42 +00:00
|
|
|
pub struct PostgresResult<'self> {
|
2013-09-04 05:05:04 +00:00
|
|
|
priv stmt: &'self NormalPostgresStatement<'self>,
|
2013-09-04 05:42:24 +00:00
|
|
|
priv name: ~str,
|
|
|
|
priv data: RingBuf<~[Option<~[u8]>]>,
|
|
|
|
priv row_limit: uint,
|
|
|
|
priv more_rows: bool
|
|
|
|
}
|
|
|
|
|
|
|
|
#[unsafe_destructor]
|
|
|
|
impl<'self> Drop for PostgresResult<'self> {
|
|
|
|
fn drop(&self) {
|
|
|
|
do io_error::cond.trap(|_| {}).inside {
|
|
|
|
self.stmt.conn.write_messages([
|
|
|
|
&Close {
|
|
|
|
variant: 'P' as u8,
|
|
|
|
name: self.name.as_slice()
|
|
|
|
},
|
|
|
|
&Sync]);
|
|
|
|
loop {
|
|
|
|
match_read_message!(self.stmt.conn, {
|
|
|
|
ReadyForQuery {_} => break,
|
|
|
|
_ => ()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'self> PostgresResult<'self> {
|
|
|
|
fn load_rows(&mut self) {
|
|
|
|
loop {
|
|
|
|
match_read_message_or_fail!(self.stmt.conn, {
|
|
|
|
EmptyQueryResponse |
|
|
|
|
CommandComplete {_} => {
|
|
|
|
self.more_rows = false;
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
PortalSuspended => {
|
|
|
|
self.more_rows = true;
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
DataRow { row } => self.data.push_back(row)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
self.stmt.conn.wait_for_ready();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pull_rows(&mut self) {
|
|
|
|
self.stmt.conn.write_messages([
|
|
|
|
&Execute {
|
|
|
|
portal: self.name,
|
|
|
|
max_rows: self.row_limit as i32
|
|
|
|
},
|
|
|
|
&Sync]);
|
|
|
|
self.load_rows();
|
|
|
|
}
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
|
|
|
|
2013-09-02 19:42:24 +00:00
|
|
|
impl<'self> Iterator<PostgresRow<'self>> for PostgresResult<'self> {
|
|
|
|
fn next(&mut self) -> Option<PostgresRow<'self>> {
|
2013-09-04 05:42:24 +00:00
|
|
|
if self.data.is_empty() && self.more_rows {
|
|
|
|
self.pull_rows();
|
|
|
|
}
|
|
|
|
|
|
|
|
do self.data.pop_front().map_move |row| {
|
2013-09-02 19:42:24 +00:00
|
|
|
PostgresRow {
|
|
|
|
stmt: self.stmt,
|
|
|
|
data: row
|
|
|
|
}
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-02 19:42:24 +00:00
|
|
|
pub struct PostgresRow<'self> {
|
2013-09-04 05:05:04 +00:00
|
|
|
priv stmt: &'self NormalPostgresStatement<'self>,
|
2013-09-01 04:55:15 +00:00
|
|
|
priv data: ~[Option<~[u8]>]
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
|
|
|
|
2013-09-02 19:42:24 +00:00
|
|
|
impl<'self> Container for PostgresRow<'self> {
|
2013-08-23 07:13:42 +00:00
|
|
|
fn len(&self) -> uint {
|
2013-09-01 04:55:15 +00:00
|
|
|
self.data.len()
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-03 01:53:03 +00:00
|
|
|
impl<'self, I: RowIndex, T: FromSql> Index<I, T> for PostgresRow<'self> {
|
|
|
|
fn index(&self, idx: &I) -> T {
|
|
|
|
let idx = idx.idx(self.stmt);
|
2013-09-05 04:26:43 +00:00
|
|
|
FromSql::from_sql(self.stmt.result_desc[idx].ty,
|
2013-09-03 01:53:03 +00:00
|
|
|
&self.data[idx])
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-03 01:53:03 +00:00
|
|
|
pub trait RowIndex {
|
2013-09-04 05:05:04 +00:00
|
|
|
fn idx(&self, stmt: &NormalPostgresStatement) -> uint;
|
2013-09-03 01:53:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RowIndex for uint {
|
2013-09-04 05:05:04 +00:00
|
|
|
fn idx(&self, _stmt: &NormalPostgresStatement) -> uint {
|
2013-09-03 01:53:03 +00:00
|
|
|
*self
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|
2013-09-03 01:53:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This is a convenicence as the 0 in get[0] resolves to int :(
|
|
|
|
impl RowIndex for int {
|
2013-09-04 05:05:04 +00:00
|
|
|
fn idx(&self, _stmt: &NormalPostgresStatement) -> uint {
|
2013-09-03 01:53:03 +00:00
|
|
|
assert!(*self >= 0);
|
|
|
|
*self as uint
|
|
|
|
}
|
|
|
|
}
|
2013-09-03 00:07:08 +00:00
|
|
|
|
2013-09-03 01:53:03 +00:00
|
|
|
impl<'self> RowIndex for &'self str {
|
2013-09-04 05:05:04 +00:00
|
|
|
fn idx(&self, stmt: &NormalPostgresStatement) -> uint {
|
2013-09-03 01:53:03 +00:00
|
|
|
match stmt.find_col_named(*self) {
|
2013-09-03 00:07:08 +00:00
|
|
|
Some(idx) => idx,
|
2013-09-04 03:07:10 +00:00
|
|
|
None => fail2!("No column with name {}", *self)
|
2013-09-03 01:53:03 +00:00
|
|
|
}
|
2013-09-03 00:07:08 +00:00
|
|
|
}
|
2013-08-23 07:13:42 +00:00
|
|
|
}
|