Skip to content

Commit

Permalink
revamped Cursor::get_{text,blob} functions. closes linuxfood#92.
Browse files Browse the repository at this point in the history
they used to return a direct `String` or `Vec<u8>`, which may fail
when it should return `NULL`. other functions do not have this issue
since the automatic conversion is fully defined over INTEGER and
FLOAT internal types.

this commit changes them to return an optional `&str` or `&[u8]`.
this correctly handles `NULL`, and moreover, reduces the redundant
allocations when the buffer doesn't need to be kept. this also
makes `Cursor::get_bytes` fully redundant, so it has been removed.
  • Loading branch information
lifthrasiir committed Jul 26, 2014
1 parent 7ed6b73 commit e7f9813
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 25 deletions.
55 changes: 31 additions & 24 deletions src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
use ffi::*;
use libc::{c_int, c_void};
use std::collections::HashMap;
use std::string;
use std::mem::transmute;
use std::str;
use std::slice;
use std::c_str::CString;
use types::*;

/// The database cursor.
Expand Down Expand Up @@ -93,13 +95,13 @@ impl<'db> Cursor<'db> {
let mut i = 0;
let mut sqlrow = HashMap::new();
while i < column_cnt {
let name = self.get_column_name(i);
let name = self.get_column_name(i).to_string();
let coltype = self.get_column_type(i);
let res = match coltype {
SQLITE_INTEGER => sqlrow.insert(name, Integer(self.get_int(i))),
SQLITE_FLOAT => sqlrow.insert(name, Float64(self.get_f64(i))),
SQLITE_TEXT => sqlrow.insert(name, Text(self.get_text(i))),
SQLITE_BLOB => sqlrow.insert(name, Blob(self.get_blob(i))),
SQLITE_TEXT => sqlrow.insert(name, Text(self.get_text(i).unwrap().to_string())),
SQLITE_BLOB => sqlrow.insert(name, Blob(self.get_blob(i).unwrap().to_vec())),
SQLITE_NULL => sqlrow.insert(name, Null),
};
if res == false {
Expand All @@ -119,20 +121,14 @@ impl<'db> Cursor<'db> {

///
/// See http://www.sqlite.org/c3ref/column_blob.html
pub fn get_bytes(&self, i: int) -> int {
unsafe {
sqlite3_column_bytes(self.stmt, i as c_int) as int
}
}

///
/// See http://www.sqlite.org/c3ref/column_blob.html
pub fn get_blob(&self, i: int) -> Vec<u8> {
let len = self.get_bytes(i);
unsafe {
slice::raw::buf_as_slice(
sqlite3_column_blob(self.stmt, i as c_int), len as uint,
|bytes| Vec::from_slice(bytes))
pub fn get_blob<'a>(&'a self, i: int) -> Option<&'a [u8]> {
let ptr = unsafe {sqlite3_column_blob(self.stmt, i as c_int)};
let len = unsafe {sqlite3_column_bytes(self.stmt, i as c_int)} as uint;
if ptr.is_null() {
None
} else {
// make `bytes` outlive the `buf_as_slice` call
unsafe {slice::raw::buf_as_slice(ptr, len, |bytes: &[u8]| Some(transmute(bytes)))}
}
}

Expand Down Expand Up @@ -162,9 +158,18 @@ impl<'db> Cursor<'db> {

///
/// See http://www.sqlite.org/c3ref/column_blob.html
pub fn get_text(&self, i: int) -> String {
unsafe {
return string::raw::from_buf( sqlite3_column_text(self.stmt, i as c_int) as *const u8 );
pub fn get_text<'a>(&'a self, i: int) -> Option<&'a str> {
let ptr = unsafe {sqlite3_column_text(self.stmt, i as c_int)};
let len = unsafe {sqlite3_column_bytes(self.stmt, i as c_int)} as uint;
if ptr.is_null() {
None
} else {
unsafe {
slice::raw::buf_as_slice(ptr as *const u8, len, |bytes| {
let text: &str = str::raw::from_utf8(bytes);
Some(transmute(text)) // make `text` outlive the `buf_as_slice` call
})
}
}
}

Expand All @@ -189,9 +194,11 @@ impl<'db> Cursor<'db> {

/// Returns the name of the column with index `i` in the result set.
/// See http://www.sqlite.org/c3ref/column_name.html
pub fn get_column_name(&self, i: int) -> String {
pub fn get_column_name<'a>(&'a self, i: int) -> &'a str {
unsafe {
return string::raw::from_buf( sqlite3_column_name(self.stmt, i as c_int) as *const u8 );
let name = CString::new(sqlite3_column_name(self.stmt, i as c_int), false);
let namestr: &str = str::raw::from_utf8(name.as_bytes_no_nul());
transmute(namestr) // make it outlive the original `CString`
}
}

Expand Down Expand Up @@ -219,7 +226,7 @@ impl<'db> Cursor<'db> {
let mut i = 0;
let mut r = Vec::new();
while i < cnt {
r.push(self.get_column_name(i));
r.push(self.get_column_name(i).to_string());
i += 1;
}
return r;
Expand Down
17 changes: 16 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ mod tests {

let sth = checked_prepare(&database, "SELECT v FROM test WHERE id = 1;");
assert!(sth.step() == SQLITE_ROW);
assert!(sth.get_blob(0) == vec!(0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff));
assert!(sth.get_blob(0) == Some([0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff].as_slice()));
assert!(sth.step() == SQLITE_DONE);
}

Expand Down Expand Up @@ -343,5 +343,20 @@ mod tests {
return r.unwrap() == v;
}
}

#[test]
fn get_text_without_step() {
let db = checked_open();
let c = checked_prepare(&db, "select 1 + 1");
assert_eq!(c.get_text(0), None);
}

#[test]
fn get_text_on_bogus_col() {
let db = checked_open();
let c = checked_prepare(&db, "select 1 + 1");
c.step();
assert_eq!(c.get_text(1), None);
}
}

0 comments on commit e7f9813

Please sign in to comment.