Null support and Makefile

This commit is contained in:
Steven Fackler
2013-08-04 20:48:48 -04:00
parent d6073039df
commit 17199139c9
6 changed files with 154 additions and 41 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
*.so
*.dummy
/postgres
/test*

24
Makefile Normal file
View File

@@ -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

View File

@@ -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<T>(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<uint, Option<T>> for PostgresRow<'self> {
fn index(&self, idx: &uint) -> Option<T> {
impl<'self, T: FromSql> Index<uint, T> for PostgresRow<'self> {
fn index(&self, idx: &uint) -> T {
self.get(*idx)
}
}
impl<'self> PostgresRow<'self> {
pub fn get<T: FromStr>(&self, idx: uint) -> Option<T> {
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<T: FromSql>(&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<T>(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)

View File

@@ -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::<Option<int>>(0));
assert_eq!(Some(0), res.get(1).get::<Option<int>>(0));
Err::<(), ~str>(~"")
};
}

View File

@@ -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<Row, It, Res, S: SqlStatement<Row, It, Res>> {
fn new(uri: &str) -> Result<~Self, ~str>;
fn prepare<'a>(&'a self, query: &str) -> Result<~S, ~str>;
}
pub trait SqlStatement {
fn query<T>(&self, params: &[@SqlType],
blk: &fn(&SqlResult) -> Result<T, ~str>)
pub trait SqlStatement<Row, It, R: SqlResult<Row, It>> {
fn query<T>(&self, params: &[~str], blk: &fn(&R) -> Result<T, ~str>)
-> Result<T, ~str>;
fn update(&self, params: &[@SqlType]) -> Result<uint, ~str>;
fn update(&self, params: &[~str]) -> Result<uint, ~str>;
}
pub trait SqlResult<R: SqlRow, I: Iterator<R>>: Container {
fn iter(&self) -> I;
}
pub trait SqlRow {
fn get<T: SqlType>(&self) -> Option<T>;
pub trait SqlRow: Container {
fn get<T: FromSql<Self>>(&self, index: uint) -> Option<T>;
}
pub enum SqlType {
Int(int),
Uint(uint),
pub trait FromSql<R/*: SqlRow*/> {
fn from_sql(row: &R, column: uint) -> Option<Self>;
}

View File

@@ -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)
};