Skip to content

Commit af2db76

Browse files
committed
Proper NULL support
1 parent 762a55b commit af2db76

2 files changed

Lines changed: 87 additions & 24 deletions

File tree

src/sqlite3/lib.rs

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::libc::c_int;
22
use std::ptr;
33
use std::str;
4+
use std::vec;
5+
use std::uint;
46

57
mod ffi {
68
use std::libc::{c_char, c_int, c_void};
@@ -13,6 +15,8 @@ mod ffi {
1315
pub static SQLITE_ROW: c_int = 100;
1416
pub static SQLITE_DONE: c_int = 101;
1517

18+
pub static SQLITE_NULL: c_int = 5;
19+
1620
// A function because Rust doesn't like casting from an int to a function
1721
// pointer in a static declaration
1822
pub fn SQLITE_TRANSIENT() -> extern "C" fn(*c_void) {
@@ -31,8 +35,10 @@ mod ffi {
3135
fn sqlite3_reset(pStmt: *sqlite3_stmt) -> c_int;
3236
fn sqlite3_bind_text(pStmt: *sqlite3_stmt, idx: c_int, text: *c_char,
3337
n: c_int, free: extern "C" fn(*c_void)) -> c_int;
38+
fn sqlite3_bind_null(pStmt: *sqlite3_stmt, idx: c_int) -> c_int;
3439
fn sqlite3_step(pStmt: *sqlite3_stmt) -> c_int;
3540
fn sqlite3_column_count(pStmt: *sqlite3_stmt) -> c_int;
41+
fn sqlite3_column_type(pStmt: *sqlite3_stmt, iCol: c_int) -> c_int;
3642
fn sqlite3_column_text(pStmt: *sqlite3_stmt, iCol: c_int) -> *c_char;
3743
fn sqlite3_finalize(pStmt: *sqlite3_stmt) -> c_int;
3844
}
@@ -161,11 +167,16 @@ impl<'self> PreparedStatement<'self> {
161167

162168
fn bind_params(&self, params: &[@SqlType]) -> Result<(), ~str> {
163169
for params.iter().enumerate().advance |(idx, param)| {
164-
let ret = do param.to_sql_str().as_c_str |c_param| {
165-
unsafe {
166-
ffi::sqlite3_bind_text(self.stmt, (idx+1) as c_int,
167-
c_param, -1,
168-
ffi::SQLITE_TRANSIENT())
170+
let ret = match param.to_sql_str() {
171+
Some(val) => do val.as_c_str |c_param| {
172+
unsafe {
173+
ffi::sqlite3_bind_text(self.stmt, (idx+1) as c_int,
174+
c_param, -1,
175+
ffi::SQLITE_TRANSIENT())
176+
}
177+
},
178+
None => unsafe {
179+
ffi::sqlite3_bind_null(self.stmt, (idx+1) as c_int)
169180
}
170181
};
171182

@@ -218,15 +229,16 @@ impl<'self> Iterator<Row<'self>> for ResultIterator<'self> {
218229
fn next(&mut self) -> Option<Row<'self>> {
219230
let ret = unsafe { ffi::sqlite3_step(self.stmt.stmt) };
220231
match ret {
221-
ffi::SQLITE_ROW => Some(Row {stmt: self.stmt}),
232+
ffi::SQLITE_ROW => Some(Row::new(self.stmt)),
222233
// TODO: Ignoring errors for now
223234
_ => None
224235
}
225236
}
226237
}
227238

228239
pub struct Row<'self> {
229-
priv stmt: &'self PreparedStatement<'self>
240+
priv stmt: &'self PreparedStatement<'self>,
241+
priv cols: ~[Option<~str>]
230242
}
231243

232244
impl<'self> Container for Row<'self> {
@@ -240,30 +252,61 @@ impl<'self> Container for Row<'self> {
240252
}
241253

242254
impl<'self> Row<'self> {
243-
pub fn get<T: SqlType>(&self, idx: uint) -> Option<T> {
244-
let raw = unsafe {
245-
ffi::sqlite3_column_text(self.stmt.stmt, idx as c_int)
246-
};
255+
fn new(stmt: &'self PreparedStatement<'self>) -> Row<'self> {
256+
let count = unsafe { ffi::sqlite3_column_count(stmt.stmt) as uint};
257+
let mut row = Row {stmt: stmt, cols: vec::with_capacity(count)};
247258

248-
if ptr::is_null(raw) {
249-
return None;
259+
for uint::range(0, count) |i| {
260+
let typ = unsafe {
261+
ffi::sqlite3_column_type(stmt.stmt, i as c_int)
262+
};
263+
let val = match typ {
264+
ffi::SQLITE_NULL => None,
265+
_ => Some(unsafe {
266+
str::raw::from_c_str(ffi::sqlite3_column_text(stmt.stmt,
267+
i as c_int))
268+
})
269+
};
270+
row.cols.push(val);
250271
}
251272

252-
SqlType::from_sql_str(unsafe { str::raw::from_c_str(raw) })
273+
return row
274+
}
275+
}
276+
277+
impl<'self> Row<'self> {
278+
pub fn get<T: SqlType>(&self, idx: uint) -> T {
279+
SqlType::from_sql_str(&self.cols[idx])
253280
}
254281
}
255282

256283
pub trait SqlType {
257-
fn to_sql_str(&self) -> ~str;
258-
fn from_sql_str(sql_str: &str) -> Option<Self>;
284+
fn to_sql_str(&self) -> Option<~str>;
285+
fn from_sql_str(sql_str: &Option<~str>) -> Self;
259286
}
260287

261288
impl SqlType for int {
262-
fn to_sql_str(&self) -> ~str {
263-
self.to_str()
289+
fn to_sql_str(&self) -> Option<~str> {
290+
Some(self.to_str())
291+
}
292+
293+
fn from_sql_str(sql_str: &Option<~str>) -> int {
294+
FromStr::from_str(*sql_str.get_ref()).get()
295+
}
296+
}
297+
298+
impl SqlType for Option<int> {
299+
fn to_sql_str(&self) -> Option<~str> {
300+
match *self {
301+
None => None,
302+
Some(v) => Some(v.to_str())
303+
}
264304
}
265305

266-
fn from_sql_str(sql_str: &str) -> Option<int> {
267-
FromStr::from_str(sql_str)
306+
fn from_sql_str(sql_str: &Option<~str>) -> Option<int> {
307+
match *sql_str {
308+
None => None,
309+
Some(ref s) => Some(FromStr::from_str(*s).get())
310+
}
268311
}
269312
}

src/sqlite3/test.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fn test_basic() {
2121

2222
do conn.query("SELECT id FROM foo") |it| {
2323
for it.advance |row| {
24-
printfln!("%u %d", row.len(), row.get(0).get());
24+
printfln!("%u %d", row.len(), row.get(0));
2525
}
2626
};
2727
}
@@ -37,7 +37,7 @@ fn test_trans() {
3737
Err::<(), ~str>(~"")
3838
};
3939
assert_eq!(0, chk!(conn.query("SELECT COUNT(*) FROM bar", |it| {
40-
it.next().get().get(0).get()
40+
it.next().get().get(0)
4141
})));
4242

4343
do conn.in_transaction |conn| {
@@ -46,7 +46,7 @@ fn test_trans() {
4646
};
4747

4848
assert_eq!(1, chk!(conn.query("SELECT COUNT(*) FROM bar", |it| {
49-
it.next().get().get(0).get()
49+
it.next().get().get(0)
5050
})));
5151
}
5252

@@ -60,6 +60,26 @@ fn test_params() {
6060
&[@100 as @SqlType, @101 as @SqlType]));
6161

6262
assert_eq!(2, chk!(conn.query("SELECT COUNT(*) FROM foo", |it| {
63-
it.next().get().get(0).get()
63+
it.next().get().get(0)
6464
})));
6565
}
66+
67+
#[test]
68+
fn test_null() {
69+
let conn = chk!(sqlite3::open(":memory:"));
70+
chk!(conn.update("CREATE TABLE foo (
71+
id BIGINT PRIMARY KEY,
72+
n BIGINT
73+
)"));
74+
chk!(conn.update_params("INSERT INTO foo (id, n) VALUES (?, ?), (?, ?)",
75+
&[@100 as @SqlType, @None::<int> as @SqlType,
76+
@101 as @SqlType, @Some(1) as @SqlType]));
77+
78+
do conn.query("SELECT n FROM foo WHERE id = 100") |it| {
79+
assert!(it.next().get().get::<Option<int>>(0).is_none());
80+
};
81+
82+
do conn.query("SELECT n FROM foo WHERE id = 101") |it| {
83+
assert_eq!(Some(1), it.next().get().get(0))
84+
};
85+
}

0 commit comments

Comments
 (0)