diff --git a/.gitignore b/.gitignore index 43560656..ce200c7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.so +*.dummy /postgres +/test* diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..b108e00d --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +RUSTC ?= rustc +RUSTFLAGS += -L. + +.PHONY: all +all: sql.dummy postgres.dummy test_postgres sqlite3.dummy + +sql.dummy: src/sql/lib.rs + $(RUSTC) $(RUSTFLAGS) --lib $< -o $@ + touch $@ + +postgres.dummy: src/postgres/lib.rs sql.dummy + $(RUSTC) $(RUSTFLAGS) --lib src/postgres/lib.rs -o $@ + touch $@ + +test_postgres: src/postgres/test.rs postgres.dummy + $(RUSTC) $(RUSTFLAGS) --test src/postgres/test.rs -o $@ + +sqlite3.dummy: src/sqlite3/lib.rs sql.dummy + $(RUSTC) $(RUSTFLAGS) --lib src/sqlite3/lib.rs -o $@ + touch $@ + +.PHONY: clean +clean: + rm *.dummy *.so diff --git a/src/postgres/lib.rs b/src/postgres/lib.rs index ba9dd474..7e5f5bff 100644 --- a/src/postgres/lib.rs +++ b/src/postgres/lib.rs @@ -47,6 +47,8 @@ mod ffi { 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; } @@ -242,6 +244,18 @@ impl<'self> PostgresStatement<'self> { } } +fn as_c_str_array(array: &[~str], blk: &fn(**c_char) -> T) -> T { + let mut c_array: ~[*c_char] = vec::with_capacity(array.len() + 1); + for s in array.iter() { + // DANGER, WILL ROBINSON + do s.as_c_str |c_s| { + c_array.push(c_s); + } + } + c_array.push(ptr::null()); + blk(vec::raw::to_ptr(c_array)) +} + pub struct PostgresResult { priv result: *ffi::PGresult } @@ -275,6 +289,21 @@ impl PostgresResult { fn num_params(&self) -> uint { unsafe { ffi::PQnparams(self.result) as uint } } + + fn is_null(&self, row: uint, col: uint) -> bool { + unsafe { + ffi::PQgetisnull(self.result, row as c_int, col as c_int) == 1 + } + } + + fn get_value(&self, row: uint, col: uint) -> ~str { + unsafe { + let raw_s = ffi::PQgetvalue(self.result, + row as c_int, + col as c_int); + str::raw::from_c_str(raw_s) + } + } } impl Container for PostgresResult { @@ -345,37 +374,84 @@ impl<'self> Container for PostgresRow<'self> { } } -impl<'self, T: FromStr> Index> for PostgresRow<'self> { - fn index(&self, idx: &uint) -> Option { +impl<'self, T: FromSql> Index for PostgresRow<'self> { + fn index(&self, idx: &uint) -> T { self.get(*idx) } } impl<'self> PostgresRow<'self> { - pub fn get(&self, idx: uint) -> Option { + fn is_null(&self, col: uint) -> bool { + self.result.is_null(self.row, col) + } + + fn get_value(&self, col: uint) -> ~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"); } - - let s = unsafe { - let raw_s = ffi::PQgetvalue(self.result.result, - self.row as c_int, - idx as c_int); - str::raw::from_c_str(raw_s) - }; - - FromStr::from_str(s) + FromSql::from_sql(self, idx) } } -fn as_c_str_array(array: &[~str], blk: &fn(**c_char) -> T) -> T { - let mut c_array: ~[*c_char] = vec::with_capacity(array.len() + 1); - for s in array.iter() { - // DANGER, WILL ROBINSON - do s.as_c_str |c_s| { - c_array.push(c_s); +pub trait FromSql { + fn from_sql(row: &PostgresRow, idx: uint) -> Self; +} + +macro_rules! from_str_impl( + ($t:ty) => ( + impl FromSql for $t { + fn from_sql(row: &PostgresRow, idx: uint) -> $t { + if row.is_null(idx) { + fail!("Row is NULL"); + } + FromStr::from_str(row.get_value(idx)).get() + } } - } - c_array.push(ptr::null()); - blk(vec::raw::to_ptr(c_array)) -} + ) +) + +macro_rules! option_impl( + ($t:ty) => ( + impl FromSql for Option<$t> { + fn from_sql(row: &PostgresRow, idx: uint) -> Option<$t> { + match row.is_null(idx) { + true => None, + false => Some(FromSql::from_sql(row, idx)) + } + } + } + ) +) + +from_str_impl!(int) +option_impl!(int) +from_str_impl!(i8) +option_impl!(i8) +from_str_impl!(i16) +option_impl!(i16) +from_str_impl!(i32) +option_impl!(i32) +from_str_impl!(i64) +option_impl!(i64) +from_str_impl!(uint) +option_impl!(uint) +from_str_impl!(u8) +option_impl!(u8) +from_str_impl!(u16) +option_impl!(u16) +from_str_impl!(u32) +option_impl!(u32) +from_str_impl!(u64) +option_impl!(u64) +from_str_impl!(float) +option_impl!(float) +from_str_impl!(f32) +option_impl!(f32) +from_str_impl!(f64) +option_impl!(f64) diff --git a/src/postgres/test.rs b/src/postgres/test.rs index 93292e09..c1ae61f2 100644 --- a/src/postgres/test.rs +++ b/src/postgres/test.rs @@ -38,10 +38,26 @@ fn test_params() { chk!(conn.update("INSERT INTO basic (id) VALUES ($1)", [~"101"])); let res = chk!(conn.query("SELECT id from basic WHERE id = $1", [~"101"])); - assert_eq!(1, res.len()); - assert_eq!(1, res.get(0).len()); 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, NULL), ($2, $3)", + [~"101", ~"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>(~"") + }; +} diff --git a/src/sql/lib.rs b/src/sql/lib.rs index b7f25cd4..e2476dca 100644 --- a/src/sql/lib.rs +++ b/src/sql/lib.rs @@ -1,27 +1,23 @@ -pub trait SqlConnection<'self, S: SqlStatement> { - fn new(uri: &str) -> ~Self; - - fn prepare(&self, query: &str) -> ~S<'self>; +pub trait SqlConnection> { + fn new(uri: &str) -> Result<~Self, ~str>; + fn prepare<'a>(&'a self, query: &str) -> Result<~S, ~str>; } -pub trait SqlStatement { - fn query(&self, params: &[@SqlType], - blk: &fn(&SqlResult) -> Result) +pub trait SqlStatement> { + fn query(&self, params: &[~str], blk: &fn(&R) -> Result) -> Result; - - fn update(&self, params: &[@SqlType]) -> Result; + fn update(&self, params: &[~str]) -> Result; } pub trait SqlResult>: Container { fn iter(&self) -> I; } -pub trait SqlRow { - fn get(&self) -> Option; +pub trait SqlRow: Container { + fn get>(&self, index: uint) -> Option; } -pub enum SqlType { - Int(int), - Uint(uint), +pub trait FromSql { + fn from_sql(row: &R, column: uint) -> Option; } diff --git a/src/sqlite3/lib.rs b/src/sqlite3/lib.rs index 93e0141e..2b068730 100644 --- a/src/sqlite3/lib.rs +++ b/src/sqlite3/lib.rs @@ -2,7 +2,6 @@ use std::libc::c_int; use std::ptr; use std::str; use std::vec; -use std::uint; mod ffi { use std::libc::{c_char, c_int, c_void}; @@ -165,7 +164,7 @@ impl<'self> PreparedStatement<'self> { } fn bind_params(&self, params: &[@SqlType]) -> Result<(), ~str> { - for params.iter().enumerate().advance |(idx, param)| { + for (idx, param) in params.iter().enumerate() { let ret = match param.to_sql_str() { Some(val) => do val.as_c_str |c_param| { unsafe { @@ -246,7 +245,7 @@ impl<'self> Row<'self> { let count = unsafe { ffi::sqlite3_column_count(stmt.stmt) as uint}; let mut row = Row {stmt: stmt, cols: vec::with_capacity(count)}; - for uint::range(0, count) |i| { + for i in range(0, count) { let typ = unsafe { ffi::sqlite3_column_type(stmt.stmt, i as c_int) };