diff --git a/Makefile b/Makefile index 3544ff8c..ba734a8f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ RUSTC ?= rustc -RUSTFLAGS += -L. +RUSTFLAGS += -L. --cfg debug .PHONY: all all: postgres.dummy -postgres.dummy: src/lib.rs +postgres.dummy: src/lib.rs src/message.rs $(RUSTC) $(RUSTFLAGS) --lib src/lib.rs -o $@ touch $@ diff --git a/src/lib.rs b/src/lib.rs index 2d295a88..3e17993e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,537 +1,107 @@ +extern mod extra; + use std::cell::Cell; -use std::c_str::{ToCStr, CString}; -use std::str; -use std::ptr; -use std::libc::{c_void, c_char, c_int}; -use std::iterator::RandomAccessIterator; -use std::vec; +use std::hashmap::HashMap; +use std::rt::io::io_error; +use std::rt::io::net::ip::SocketAddr; +use std::rt::io::net::tcp::TcpStream; +use extra::url::Url; -mod ffi { - use std::libc::{c_char, c_int, c_uchar, c_uint, c_void}; +use message::*; - pub type PGconn = c_void; - pub type PGresult = c_void; - pub type Oid = c_uint; - - pub static CONNECTION_OK: c_int = 0; - - pub static PGRES_EMPTY_QUERY: c_int = 0; - pub static PGRES_COMMAND_OK: c_int = 1; - pub static PGRES_TUPLES_OK: c_int = 2; - - pub static TEXT_FORMAT: c_int = 0; - - // FIXME when FFI gets fixed - type PQnoticeProcessor = *c_uchar /*extern "C" fn(*c_void, *c_char)*/; - - #[link_args = "-lpq"] - extern "C" { - fn PQconnectdb(conninfo: *c_char) -> *PGconn; - fn PQsetNoticeProcessor(conn: *PGconn, proc: PQnoticeProcessor, - arg: *c_void) -> PQnoticeProcessor; - fn PQfinish(conn: *PGconn); - fn PQstatus(conn: *PGconn) -> c_int; - fn PQerrorMessage(conn: *PGconn) -> *c_char; - fn PQexec(conn: *PGconn, query: *c_char) -> *PGresult; - fn PQresultStatus(result: *PGresult) -> c_int; - fn PQresultErrorMessage(result: *PGresult) -> *c_char; - fn PQclear(result: *PGresult); - fn PQprepare(conn: *PGconn, stmtName: *c_char, query: *c_char, - nParams: c_int, paramTypes: *Oid) -> *PGresult; - fn PQdescribePrepared(conn: *PGconn, stmtName: *c_char) -> *PGresult; - fn PQexecPrepared(conn: *PGconn, stmtName: *c_char, nParams: c_int, - paramValues: **c_char, paramLengths: *c_int, - paramFormats: *c_int, resultFormat: c_int) - -> *PGresult; - fn PQntuples(result: *PGresult) -> c_int; - fn PQnfields(result: *PGresult) -> c_int; - fn PQnparams(result: *PGresult) -> c_int; - fn PQcmdTuples(result: *PGresult) -> *c_char; - fn PQgetisnull(result: *PGresult, row_number: c_int, col_number: c_int) - -> c_int; - fn PQgetvalue(result: *PGresult, row_number: c_int, col_number: c_int) - -> *c_char; - } -} +mod message; pub struct PostgresConnection { - priv conn: *ffi::PGconn, - priv next_stmt_id: Cell, + priv stream: Cell, + priv next_stmt_id: Cell } -extern "C" fn notice_handler(_arg: *c_void, message: *c_char) { - let message = unsafe { str::raw::from_c_str(message) }; - if message.starts_with("DEBUG") { - debug!("%s", message); - } else if message.starts_with("NOTICE") || - message.starts_with("INFO") || - message.starts_with("LOG") { - info!("%s", message); - } else if message.starts_with("WARNING") { - warn!("%s", message); - } else { - error!("%s", message); - } -} - -#[unsafe_destructor] impl Drop for PostgresConnection { fn drop(&self) { - unsafe { ffi::PQfinish(self.conn) } + do io_error::cond.trap(|_| { }).inside { + do self.stream.with_mut_ref |s| { + s.write_message(&Terminate); + } + } } } impl PostgresConnection { - fn status(&self) -> c_int { - unsafe { ffi::PQstatus(self.conn) } - } + pub fn connect(url: &str, username: &str) -> PostgresConnection { + let parsed_url: Url = FromStr::from_str(url).unwrap(); - fn get_error(&self) -> ~str { - unsafe { str::raw::from_c_str(ffi::PQerrorMessage(self.conn)) } - } - - fn set_notice_processor(&mut self, - handler: *u8 /* extern "C" fn(*c_void, *c_char) */, - arg: *c_void) { - unsafe { ffi::PQsetNoticeProcessor(self.conn, handler, arg); } - } -} - -impl PostgresConnection { - pub fn new(uri: &str) -> Result<~PostgresConnection, ~str> { - let mut conn = ~PostgresConnection { - conn: do uri.with_c_str |c_uri| { - unsafe { ffi::PQconnectdb(c_uri) } - }, + let socket_url = fmt!("%s:%s", parsed_url.host, + parsed_url.port.unwrap_or_default(~"5432")); + let addr: SocketAddr = FromStr::from_str(socket_url).unwrap(); + let conn = PostgresConnection { + stream: Cell::new(TcpStream::connect(addr).unwrap()), next_stmt_id: Cell::new(0) }; - conn.set_notice_processor(notice_handler, ptr::null()); + do conn.stream.with_mut_ref |s| { + let mut args = HashMap::new(); + args.insert(~"user", username.to_owned()); + s.write_message(&StartupMessage(args)); + } - match conn.status() { - ffi::CONNECTION_OK => Ok(conn), - _ => Err(conn.get_error()) + let resp = do conn.stream.with_mut_ref |s| { + s.read_message() + }; + match resp { + AuthenticationOk => (), + _ => fail!("Bad response: %?", resp) + } + + conn.finish_connect(); + + conn + } + + fn finish_connect(&self) { + loop { + match self.stream.with_mut_ref(|s| { s.read_message() }) { + ParameterStatus(param, value) => + printfln!("Param %s = %s", param, value), + BackendKeyData(*) => loop, + ReadyForQuery(_) => break, + resp => fail!("Bad response: %?", resp) + } } } - pub fn prepare<'a>(&'a self, query: &str) - -> Result<~PostgresStatement<'a>, ~str> { + pub fn prepare<'a>(&'a self, query: &str) -> PostgresStatement<'a> { let id = self.next_stmt_id.take(); - let name = fmt!("__libpostgres_stmt_%u", id); + let query_name = ifmt!("statement_{}", id); self.next_stmt_id.put_back(id + 1); - let mut res = unsafe { - let raw_res = do query.with_c_str |c_query| { - do name.with_c_str |c_name| { - ffi::PQprepare(self.conn, c_name, c_query, - 0, ptr::null()) - } - }; - PostgresResult {result: raw_res} - }; - - if res.status() != ffi::PGRES_COMMAND_OK { - return Err(res.error()); + do self.stream.with_mut_ref |s| { + s.write_message(&Parse(query_name.clone(), query.to_owned(), ~[])); } - res = unsafe { - let raw_res = do name.with_c_str |c_name| { - ffi::PQdescribePrepared(self.conn, c_name) - }; - PostgresResult {result: raw_res} - }; - - if res.status() != ffi::PGRES_COMMAND_OK { - return Err(res.error()); + do self.stream.with_mut_ref |s| { + s.write_message(&Sync); } - Ok(~PostgresStatement {conn: self, name: name, - num_params: res.num_params()}) - } - - pub fn update(&self, query: &str, params: &[&ToSql]) - -> Result { - do self.prepare(query).chain |stmt| { - stmt.update(params) - } - } - - pub fn query(&self, query: &str, params: &[&ToSql]) - -> Result<~PostgresResult, ~str> { - do self.prepare(query).chain |stmt| { - stmt.query(params) - } - } - - pub fn in_transaction(&self, - blk: &fn(&PostgresConnection) -> Result) - -> Result { - match self.update("BEGIN", []) { - Ok(_) => (), - Err(err) => return Err(err) - }; - - // If the task fails in blk, the transaction will roll back when the - // connection closes - let ret = blk(self); - - // TODO What to do about errors here? - if ret.is_ok() { - self.update("COMMIT", []); - } else { - self.update("ROLLBACK", []); + match self.stream.with_mut_ref(|s| { s.read_message() }) { + ParseComplete => (), + ErrorResponse(ref data) => fail!("Error: %?", data), + resp => fail!("Bad response: %?", resp) } - ret + match self.stream.with_mut_ref(|s| { s.read_message() }) { + ReadyForQuery(*) => (), + resp => fail!("Bad response: %?", resp) + } + + PostgresStatement { conn: self, name: query_name } } } pub struct PostgresStatement<'self> { priv conn: &'self PostgresConnection, - priv name: ~str, - priv num_params: uint -} - -#[unsafe_destructor] -impl<'self> Drop for PostgresStatement<'self> { - fn drop(&self) { - // We can't do self.conn.update(...) since that will create a statement - let query = fmt!("DEALLOCATE %s", self.name); - unsafe { - do query.with_c_str |c_query| { - ffi::PQclear(ffi::PQexec(self.conn.conn, c_query)); - } - } - } + priv name: ~str } impl<'self> PostgresStatement<'self> { - fn exec(&self, params: &[&ToSql]) -> Result<~PostgresResult, ~str> { - if params.len() != self.num_params { - return Err(~"Incorrect number of parameters"); - } - let res = unsafe { - let raw_res = do self.name.with_c_str |c_name| { - let str_params: ~[Option<~str>] = do params.map |param| { - param.to_sql() - }; - do with_c_str_array(str_params) |c_params| { - ffi::PQexecPrepared(self.conn.conn, c_name, - self.num_params as c_int, - c_params, ptr::null(), ptr::null(), - ffi::TEXT_FORMAT) - } - }; - ~PostgresResult{result: raw_res} - }; - - match res.status() { - ffi::PGRES_EMPTY_QUERY | - ffi::PGRES_COMMAND_OK | - ffi::PGRES_TUPLES_OK => Ok(res), - _ => Err(res.error()) - } - } - - pub fn update(&self, params: &[&ToSql]) -> Result { - do self.exec(params).chain |res| { - Ok(res.affected_rows()) - } - } - - pub fn query(&self, params: &[&ToSql]) -> Result<~PostgresResult, ~str> { - do self.exec(params).chain |res| { - Ok(res) - } - } -} - -fn with_c_str_array(array: &[Option<~str>], blk: &fn(**c_char) -> T) -> T { - let mut cstrs: ~[CString] = ~[]; - let mut c_array: ~[*c_char] = ~[]; - for s in array.iter() { - match *s { - None => { - c_array.push(ptr::null()) - } - Some(ref s) => { - cstrs.push(s.to_c_str()); - do cstrs.last().with_ref |c_str| { - c_array.push(c_str); - } - } - } - } - blk(vec::raw::to_ptr(c_array)) -} - -pub struct PostgresResult { - priv result: *ffi::PGresult -} - -impl Drop for PostgresResult { - fn drop(&self) { - unsafe { ffi::PQclear(self.result) } - } -} - -impl PostgresResult { - fn status(&self) -> c_int { - unsafe { ffi::PQresultStatus(self.result) } - } - - fn error(&self) -> ~str { - unsafe { str::raw::from_c_str(ffi::PQresultErrorMessage(self.result)) } - } - - fn affected_rows(&self) -> uint { - let s = unsafe { - str::raw::from_c_str(ffi::PQcmdTuples(self.result)) - }; - - match FromStr::from_str(s) { - Some(updates) => updates, - None => 0 - } - } - - fn num_params(&self) -> uint { - unsafe { ffi::PQnparams(self.result) as uint } - } - - fn get_value(&self, row: uint, col: uint) -> Option<~str> { - unsafe { - match ffi::PQgetisnull(self.result, row as c_int, col as c_int) { - 0 => { - let raw_s = ffi::PQgetvalue(self.result, - row as c_int, - col as c_int); - Some(str::raw::from_c_str(raw_s)) - } - _ => None - } - } - } -} - -impl Container for PostgresResult { - fn len(&self) -> uint { - unsafe { ffi::PQntuples(self.result) as uint } - } -} - -impl PostgresResult { - pub fn iter<'a>(&'a self) -> PostgresResultIterator<'a> { - PostgresResultIterator {result: self, next_row: 0} - } - - pub fn get<'a>(&'a self, idx: uint) -> PostgresRow<'a> { - if idx >= self.len() { - fail!("Out of bounds access"); - } - - self.iter().idx(idx).unwrap() - } -} - -pub struct PostgresResultIterator<'self> { - priv result: &'self PostgresResult, - priv next_row: uint -} - -impl<'self> Iterator> for PostgresResultIterator<'self> { - fn next(&mut self) -> Option> { - if self.result.len() == self.next_row { - return None; - } - - let row = self.next_row; - self.next_row += 1; - Some(PostgresRow {result: self.result, row: row}) - } - - fn size_hint(&self) -> (uint, Option) { - let rem = self.result.len() - self.next_row; - (rem, Some(rem)) - } -} - -impl<'self> RandomAccessIterator> for - PostgresResultIterator<'self> { - fn indexable(&self) -> uint { - self.result.len() - } - - fn idx(&self, idx: uint) -> Option> { - if idx < self.indexable() { - Some(PostgresRow {result: self.result, row: idx}) - } else { - None - } - } -} - -pub struct PostgresRow<'self> { - priv result: &'self PostgresResult, - priv row: uint -} - -impl<'self> Container for PostgresRow<'self> { - fn len(&self) -> uint { - unsafe { ffi::PQnfields(self.result.result) as uint } - } -} - -impl<'self, T: FromSql> Index for PostgresRow<'self> { - fn index(&self, idx: &uint) -> T { - self.get(*idx) - } -} - -impl<'self> PostgresRow<'self> { - fn get_value(&self, col: uint) -> Option<~str> { - self.result.get_value(self.row, col) - } -} - -impl<'self> PostgresRow<'self> { - pub fn get(&self, idx: uint) -> T { - if idx >= self.len() { - fail!("Out of bounds access"); - } - FromSql::from_sql(self, idx) - } -} - -pub trait FromSql { - fn from_sql(row: &PostgresRow, idx: uint) -> Self; -} - -macro_rules! from_opt_impl( - ($t:ty) => ( - impl FromSql for $t { - fn from_sql(row: &PostgresRow, idx: uint) -> $t { - FromSql::from_sql::>(row, idx).unwrap() - } - } - ) -) - -macro_rules! from_str_opt_impl( - ($t:ty) => ( - impl FromSql for Option<$t> { - fn from_sql(row: &PostgresRow, idx: uint) -> Option<$t> { - do row.get_value(idx).chain |s| { - Some(FromStr::from_str(s).unwrap()) - } - } - } - ) -) - -from_opt_impl!(int) -from_str_opt_impl!(int) -from_opt_impl!(i8) -from_str_opt_impl!(i8) -from_opt_impl!(i16) -from_str_opt_impl!(i16) -from_opt_impl!(i32) -from_str_opt_impl!(i32) -from_opt_impl!(i64) -from_str_opt_impl!(i64) -from_opt_impl!(uint) -from_str_opt_impl!(uint) -from_opt_impl!(u8) -from_str_opt_impl!(u8) -from_opt_impl!(u16) -from_str_opt_impl!(u16) -from_opt_impl!(u32) -from_str_opt_impl!(u32) -from_opt_impl!(u64) -from_str_opt_impl!(u64) -from_opt_impl!(float) -from_str_opt_impl!(float) -from_opt_impl!(f32) -from_str_opt_impl!(f32) -from_opt_impl!(f64) -from_str_opt_impl!(f64) - -impl FromSql for Option<~str> { - fn from_sql(row: &PostgresRow, idx: uint) -> Option<~str> { - row.get_value(idx) - } -} -from_opt_impl!(~str) - -pub trait ToSql { - fn to_sql(&self) -> Option<~str>; -} - -macro_rules! to_str_impl( - ($t:ty) => ( - impl ToSql for $t { - fn to_sql(&self) -> Option<~str> { - Some(self.to_str()) - } - } - ) -) - -macro_rules! to_str_opt_impl( - ($t:ty) => ( - impl ToSql for Option<$t> { - fn to_sql(&self) -> Option<~str> { - match *self { - Some(ref val) => Some(val.to_sql().unwrap()), - None => None - } - } - } - ) -) - -to_str_impl!(int) -to_str_opt_impl!(int) -to_str_impl!(i8) -to_str_opt_impl!(i8) -to_str_impl!(i16) -to_str_opt_impl!(i16) -to_str_impl!(i32) -to_str_opt_impl!(i32) -to_str_impl!(i64) -to_str_opt_impl!(i64) -to_str_impl!(uint) -to_str_opt_impl!(uint) -to_str_impl!(u8) -to_str_opt_impl!(u8) -to_str_impl!(u16) -to_str_opt_impl!(u16) -to_str_impl!(u32) -to_str_opt_impl!(u32) -to_str_impl!(u64) -to_str_opt_impl!(u64) -to_str_impl!(float) -to_str_opt_impl!(float) -to_str_impl!(f32) -to_str_opt_impl!(f32) -to_str_impl!(f64) -to_str_opt_impl!(f64) - -impl<'self> ToSql for &'self str { - fn to_sql(&self) -> Option<~str> { - Some(self.to_str()) - } -} - -impl ToSql for ~str { - fn to_sql(&self) -> Option<~str> { - Some(self.clone()) - } -} - -impl ToSql for Option<~str> { - fn to_sql(&self) -> Option<~str> { - self.clone() - } } diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 00000000..76b3863d --- /dev/null +++ b/src/message.rs @@ -0,0 +1,160 @@ +use std::str; +use std::rt::io::{Decorator, Reader, Writer}; +use std::rt::io::extensions::{ReaderUtil, ReaderByteConversions, + WriterByteConversions}; +use std::rt::io::mem::{MemWriter, MemReader}; +use std::hashmap::HashMap; +use std::sys; + +pub static PROTOCOL_VERSION: i32 = 0x0003_0000; + +pub enum BackendMessage { + AuthenticationOk, + BackendKeyData(i32, i32), + ErrorResponse(HashMap), + ParameterStatus(~str, ~str), + ParseComplete, + ReadyForQuery(u8) +} + +pub enum FrontendMessage { + /// name, query, parameter types + Parse(~str, ~str, ~[i32]), + Query(~str), + StartupMessage(HashMap<~str, ~str>), + Sync, + Terminate +} + +trait WriteString { + fn write_string(&mut self, s: &str); +} + +impl WriteString for W { + fn write_string(&mut self, s: &str) { + self.write(s.as_bytes()); + self.write_u8_(0); + } +} + +pub trait WriteMessage { + fn write_message(&mut self, &FrontendMessage); +} + +impl WriteMessage for W { + fn write_message(&mut self, message: &FrontendMessage) { + debug!("Writing message %?", message); + let mut buf = MemWriter::new(); + let mut ident = None; + + match *message { + Parse(ref name, ref query, ref param_types) => { + ident = Some('P'); + buf.write_string(*name); + buf.write_string(*query); + buf.write_be_i16_(param_types.len() as i16); + for ty in param_types.iter() { + buf.write_be_i32_(*ty); + } + } + Query(ref query) => { + ident = Some('Q'); + buf.write_string(*query); + } + StartupMessage(ref params) => { + buf.write_be_i32_(PROTOCOL_VERSION); + for (k, v) in params.iter() { + buf.write_string(*k); + buf.write_string(*v); + } + buf.write_u8_(0); + } + Sync => { + ident = Some('S'); + } + Terminate => { + ident = Some('X'); + } + } + + match ident { + Some(ident) => self.write_u8_(ident as u8), + None => () + } + + // add size of length value + self.write_be_i32_((buf.inner_ref().len() + sys::size_of::()) + as i32); + self.write(buf.inner()); + } +} + +trait ReadString { + fn read_string(&mut self) -> ~str; +} + +impl ReadString for R { + fn read_string(&mut self) -> ~str { + let mut buf = ~[]; + loop { + let byte = self.read_u8_(); + if byte == 0 { + break; + } + + buf.push(byte); + } + + str::from_bytes_owned(buf) + } +} + +pub trait ReadMessage { + fn read_message(&mut self) -> BackendMessage; +} + +impl ReadMessage for R { + fn read_message(&mut self) -> BackendMessage { + debug!("Reading message"); + + let ident = self.read_u8_(); + debug!("Ident %?", ident); + // subtract size of length value + let len = self.read_be_i32_() as uint - sys::size_of::(); + let mut buf = MemReader::new(self.read_bytes(len)); + + let ret = match ident as char { + '1' => ParseComplete, + 'E' => read_error_message(&mut buf), + 'K' => BackendKeyData(buf.read_be_i32_(), buf.read_be_i32_()), + 'R' => read_auth_message(&mut buf), + 'S' => ParameterStatus(buf.read_string(), buf.read_string()), + 'Z' => ReadyForQuery(buf.read_u8_()), + ident => fail!("Unknown message identifier `%c`", ident) + }; + assert!(buf.eof()); + debug!("Read message %?", ret); + ret + } +} + +fn read_error_message(buf: &mut MemReader) -> BackendMessage { + let mut fields = HashMap::new(); + loop { + let ty = buf.read_u8_(); + if ty == 0 { + break; + } + + fields.insert(ty, buf.read_string()); + } + + ErrorResponse(fields) +} + +fn read_auth_message(buf: &mut MemReader) -> BackendMessage { + match buf.read_be_i32_() { + 0 => AuthenticationOk, + val => fail!("Unknown Authentication identifier `%?`", val) + } +} diff --git a/src/test.rs b/src/test.rs index 34779a71..a6cf2eff 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,115 +1,11 @@ extern mod postgres; -use postgres::{PostgresConnection, ToSql}; - -macro_rules! params( - ($($e:expr),+) => ( - [$( - $e as &ToSql - ),+] - ) -) - -macro_rules! chk( - ($e:expr) => ( - match $e { - Ok(ok) => ok, - Err(err) => fail!(err) - } - ) -) +use postgres::PostgresConnection; #[test] -fn test_basic() { - let conn = chk!(PostgresConnection::new("postgres://postgres@localhost")); +fn test_connect() { + let conn = PostgresConnection::connect("postgres://127.0.0.1:54322", + "postgres"); - do conn.in_transaction |conn| { - chk!(conn.update("CREATE TABLE basic (id INT PRIMARY KEY)", [])); - chk!(conn.update("INSERT INTO basic (id) VALUES (101)", [])); - - let res = chk!(conn.query("SELECT id from basic WHERE id = 101", [])); - assert_eq!(1, res.len()); - assert_eq!(1, res.get(0).len()); - assert_eq!(1, res.get(0).len()); - assert_eq!(Some(101), res.get(0)[0]); - - Err::<(), ~str>(~"") - }; -} - -#[test] -fn test_multiple_stmts() { - let conn = chk!(PostgresConnection::new("postgres://postgres@localhost")); - - do conn.in_transaction |conn| { - chk!(conn.update("CREATE TABLE foo (id INT PRIMARY KEY)", [])); - let stmt1 = chk!(conn.prepare("INSERT INTO foo (id) VALUES (101)")); - let stmt2 = chk!(conn.prepare("INSERT INTO foo (id) VALUES (102)")); - - chk!(stmt1.update([])); - chk!(stmt2.update([])); - - Err::<(), ~str>(~"") - }; -} - -#[test] -fn test_params() { - let conn = chk!(PostgresConnection::new("postgres://postgres@localhost")); - - do conn.in_transaction |conn| { - chk!(conn.update("CREATE TABLE basic (id INT PRIMARY KEY)", [])); - chk!(conn.update("INSERT INTO basic (id) VALUES ($1)", - params!(&101))); - - let res = chk!(conn.query("SELECT id from basic WHERE id = $1", - params!(&101))); - assert_eq!(Some(101), res.get(0)[0]); - - Err::<(), ~str>(~"") - }; -} - -#[test] -fn test_null() { - let conn = chk!(PostgresConnection::new("postgres://postgres@localhost")); - - do conn.in_transaction |conn| { - chk!(conn.update("CREATE TABLE basic (id INT PRIMARY KEY, foo INT)", - [])); - chk!(conn.update("INSERT INTO basic (id, foo) VALUES ($1, $2), ($3, $4)", - params!(&101, &None::, &102, &0))); - - let res = chk!(conn.query("SELECT foo from basic ORDER BY id", [])); - assert_eq!(None, res.get(0).get::>(0)); - assert_eq!(Some(0), res.get(1).get::>(0)); - - Err::<(), ~str>(~"") - }; -} - -#[test] -fn test_types() { - let conn = chk!(PostgresConnection::new("postgres://postgres@localhost")); - - do conn.in_transaction |conn| { - chk!(conn.update( - "CREATE TABLE foo ( - id INT PRIMARY KEY, - str VARCHAR, - float REAL - )", [])); - chk!(conn.update("INSERT INTO foo (id, str, float) - VALUES ($1, $2, $3), ($4, $5, $6)", - params!(&101, & &"foobar", &10.5, - &102, &None::<~str>, &None::))); - - let res = chk!(conn.query("SELECT str, float from foo ORDER BY id", [])); - assert_eq!(~"foobar", res.get(0).get::<~str>(0)); - assert_eq!(10.5, res.get(0).get::(1)); - assert_eq!(None::<~str>, res.get(1).get::>(0)); - assert_eq!(None::, res.get(1).get::>(1)); - - Err::<(), ~str>(~"") - }; + conn.prepare("CREATE TABLE foo (id BIGINT PRIMARY KEY)"); }